Skip to main content
Glama

pdml-agent

Un servidor MCP y agente de llamada a herramientas sobre el pipeline de experimentos property-driven-ml, con una puerta de control humano en el bucle para todo lo que consuma cómputo y un rastro estructurado de cada llamada.

Property-driven ML entrena clasificadores contra restricciones de lógica formal, por lo que una ejecución se define por una restricción, un conjunto de datos, una lógica diferenciable y una semilla, y produce métricas por época tanto para el rendimiento predictivo como para la seguridad de las restricciones. Esto lo convierte en un dominio genuinamente moldeado para herramientas, no en uno de demostración: los experimentos se pueden listar, las configuraciones recuperar, los resultados leer, las ejecuciones comparar y nuevas ejecuciones planificar, aprobar y ejecutar.

Estado: completo según lo previsto. Servidor, agente, puerta de control, rastreo. Ejecución real demostrada en CPU.

Arquitectura

┌──────────────────────────────────────────────────────────────┐
│  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-ml

agent.py no sabe nada del dominio. Se conecta al servidor a través de stdio como cualquier otro cliente MCP y trabaja solo con las herramientas que el servidor expone. Los módulos del dominio no tienen dependencia de MCP y se pueden probar mediante importación. server.py solo registra herramientas y delega.

Diseño

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 call

Herramientas

Herramienta

Devuelve

list_experiments

ejecuciones, filtrables por restricción, conjunto de datos o lógica

get_experiment_config

la configuración con la que realmente se entrenó una ejecución

get_results

métricas para una época, por defecto la última

compare_runs

diferencia de configuración y métricas entre dos ejecuciones

search_logic_definitions

clases de lógica, sus operadores y cadenas de documentación

run_experiment

con dry_run=true, un plan de comando validado; con dry_run=false, ejecución tras dos puertas

La puerta de control

run_experiment es la única herramienta que consume cómputo, y dos cosas independientes se interponen ante ella.

El servidor no ejecutará a menos que se haya iniciado con PDML_ALLOW_EXECUTE=1. Esa es una decisión tomada por quien ejecuta el servidor, y ninguna solicitud puede cambiarla. Sin ella, dry_run=false devuelve status: refused con el plan adjunto, y no es un error.

El agente no enviará una solicitud de ejecución sin que un operador apruebe la llamada exacta. El mensaje de aprobación muestra el nombre de la herramienta y los argumentos completos como JSON, no un resumen. Una denegación devuelve un resultado normal que lee declined_by_operator, y se instruye al modelo para que lo informe y se detenga, en lugar de reintentar.

Cualquiera de las dos capas por sí sola detendría una ejecución no deseada. Ambas juntas significan que ninguna tiene que ser perfecta. La política que decide qué necesita aprobación es una función, needs_approval, lo suficientemente pequeña como para leerla de un vistazo.

El rastro

Cada ejecución añade a traces/<timestamp>-<question>.jsonl. Una línea por evento, nunca se reescribe.

Los registros de turn llevan el número de paso, la razón de parada del modelo, su texto y resumen de pensamiento, las llamadas que está a punto de hacer y el uso de tokens de ese turno. Los registros de tool_call llevan la herramienta, sus argumentos, si la llamada tuvo éxito, vino de la caché o fue controlada, su latencia, un resumen del resultado y la razón declarada por el propio modelo, tomada de la frase que escribió junto a la llamada. Los registros de gate llevan la decisión. run_start y run_end lo enmarcan con totales.

El mensaje del sistema pide al modelo que indique en una frase por qué está haciendo cada llamada, y lo hace. Del rastro de la ruta de denegación:

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 ..."

Ese rastro también detectó un defecto en las herramientas de este propio repositorio. El turno 1 obtuvo un resultado vacío porque list_experiments filtraba por el nombre de la carpeta de resultados mientras que run_experiment tomaba el nombre de la clase, dos vocabularios para un mismo concepto. El modelo se recuperó por sí solo, a costa de un turno, y la razón de su turno 3 dice exactamente lo que dedujo. list_experiments ahora acepta cualquiera de las dos grafías.

Lo que mostraron las demostraciones

Cinco tareas, ninguna respondible en una sola llamada. Transcripciones completas en demo_output/, rastros completos en traces/.

A. Mejor lógica dentro de un presupuesto de precisión. Tres turnos. Listó las ejecuciones, obtuvo los cuatro resultados en un turno paralelo, respondió YG con 0.9981 de seguridad por 0.76 puntos de precisión, y dijo que no se ejecutó nada.

B. Planificar una variante de una ejecución existente. Cuatro turnos. Obtuvo configuración, comparación y definición de lógica en un turno paralelo, llamó a run_experiment con dry_run=true, informó el plan y el comando exacto, y, como existía una ejecución coincidente, las comparó.

C. Comparar contra una ejecución que no existe. Tres turnos. Listó primero en lugar de adivinar, confirmó que STL es una lógica real que simplemente no tiene ejecución, y lo dijo.

D. Entrenar, el operador deniega. Seis turnos. Planeó primero con una ejecución en seco, como pide la descripción de la herramienta, luego solicitó la ejecución. El aprobador denegó. El modelo informó que no se ejecutó y no reintentó, dio el plan y respondió con lo que existía.

E. Entrenar, el operador aprueba. Seis turnos, y una ejecución de entrenamiento real. Misma secuencia de planificar y luego ejecutar; el aprobador aceptó; el servidor, iniciado con la ejecución habilitada, ejecutó main.py durante una época en CPU en 28.6 segundos y escribió fixtures/results/standard-robustness/mnist/1/YG.csv. El agente luego llamó a get_results y compare_runs en la nueva ejecución e informó Test-P-Metric final 0.9160 y Test-C-Sec-self 0.5482. Ambos coinciden con el CSV. Sin que se le pidiera, enumeró los factores de confusión contra la comparación de semilla 0 (una época frente a diez, retardo, un presupuesto de ataque deliberadamente debilitado) y observó a partir de la fila de la época 0 que la seguridad de la restricción es trivialmente 1.0 en un modelo no entrenado y solo significa algo junto a una precisión convergente. Esa es una lectura correcta de la métrica.

Ese CSV de semilla 1 es una ejecución real y se mantiene junto a los fixtures sintéticos a propósito. Su primera línea es el argv con el que se entrenó, como cualquier otra ejecución.

Dos cosas que vale la pena saber sobre los datos

La época 0 es una evaluación previa al entrenamiento. Una ejecución configurada con --epochs 10 escribe once filas numeradas del 0 al 10. El número de filas y la época final se informan por separado, porque llamar al número de filas "épocas" sobreestima el entrenamiento en uno.

El script de entrenamiento escribe -1 para las métricas que no evaluó. get_results normaliza esos valores a nulo, por lo que un centinela no puede leerse como una medición. Una ejecución de referencia no tiene métricas de restricción en absoluto, y debería decirlo en lugar de informar menos uno.

Límites, declarados para no exagerar

El modelo nunca encontró un resultado de herramienta is_error en vivo en las cinco tareas, porque siguió la instrucción de listar antes de confiar en un identificador. La ruta de error se prueba a nivel de protocolo en smoke_test.py y a nivel de envoltorio, pero no se demostró la recuperación en vivo de un error de herramienta a mitad de tarea.

La memoización nunca se activó en vivo. El modelo no repitió una llamada idéntica en ninguna ejecución. Está probada unitariamente e inactiva en todos los rastros.

El almacenamiento en caché de mensajes no está configurado. cache_read_input_tokens es cero en todos los rastros, y los recuentos de tokens de entrada (11k a 46k por tarea) son en su mayoría contexto reenviado. Los puntos de interrupción de caché en las definiciones de herramientas y el mensaje del sistema lo reducirían sustancialmente y son la mejora obvia siguiente.

Ejecutar una ejecución requirió un checkout cuyo main.py analiza. En el main ascendente no lo hace: --epsilon y --delta están definidos dos veces cada uno y argparse rechaza el duplicado antes de que se lea cualquier argumento, por lo que python main.py --help falla. Eso está corregido en la rama fix/duplicate-argparse-flags del fork, con una prueba de regresión, y la demostración apuntó PDML_REPO_DIR a ese checkout.

Pruébalo

uv sync
uv run python scripts/make_fixtures.py
uv run python scripts/smoke_test.py

La prueba de humo inicia el servidor a través de stdio, enumera las herramientas, llama a cada una, comprueba que la ejecución sin PDML_ALLOW_EXECUTE es rechazada y comprueba que un id de experimento desconocido da error en lugar de tener éxito silenciosamente. No cuesta nada.

Para preguntarle algo al agente, con ANTHROPIC_API_KEY configurada:

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

Para permitirle entrenar realmente, apúntalo a un checkout de property-driven-ml cuyo main.py analice y a un intérprete con torch, luego pasa el indicador que habilita la ejecución:

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

Se te mostrará la llamada exacta y se te pedirá que la apruebes.

Variables de entorno que el servidor lee: PDML_RESULTS_DIR (dónde viven las ejecuciones, por defecto fixtures/results), PDML_REPO_DIR (el checkout de property-driven-ml), PDML_PYTHON (intérprete para main.py, si no el .venv del repositorio), PDML_ALLOW_EXECUTE (1 para permitir la ejecución), PDML_EXECUTE_TIMEOUT (segundos, por defecto 3600).

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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.

View all MCP Connectors

Latest Blog Posts

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