anti-slush, multi-essais : évaluations de préparation agentique honnêtes
Que fait agentic-evals ?
Utilisez cette compétence lorsqu'un test de fumée unique est trop faible et que la tâche nécessite une évaluation reproductible et fondée sur des preuves d'une compétence ou d'un workflow agentique.
Périmètre actuel
Ce bundle initial fournit un exécuteur de fixtures déterministe pour les cas basés sur des commandes. Il exécute chaque cas plusieurs fois, enregistre stdout/stderr/le code de sortie et la durée, vérifie les attentes explicites et émet un résumé de préparation lisible par machine.
Cela prouve uniquement le comportement déclaré de la fixture. Cela ne prouve pas l'exactitude sémantique, l'intégration réelle d'un service, la qualité d'un jugement LLM ou la préparation à la publication, à moins que les commandes de la fixture n'exercent elles-mêmes ces chemins réels.
Utilisation
./run.sh run fixtures/agentic_eval.json
./run.sh run fixtures/agentic_eval.json --output /tmp/agentic-evals-report.json
./run.sh audit-skills ../ --output /tmp/agentic-evals-baseline-gap-report.json
./run.sh scaffold-fixture ../some-skill --output ../some-skill/fixtures/agentic_eval.json
./run.sh apply-scaffolds ../ --write --output /tmp/agentic-evals-apply-scaffolds.json
# planification requirements-to-journey via découverte réelle (issue #1629)
./run.sh plan-journeys --fixture fixtures/agentic_eval.json \
--requirements requirements.json \
--interaction-inventory discovery/discovery-inventory.json \
--state-graph discovery/state-graph.json \
--output journey-plan.json [--scaffold-output fragment.json] [--report-only]
# réclamation / preuve / régression / couverture (issues #1445-#1448)
./run.sh regressions audit ../ --output /tmp/agentic-evals-regressions.json
./run.sh regressions show . && ./run.sh regressions verify .
./run.sh coverage audit ../ --output /tmp/agentic-evals-coverage.json
./run.sh coverage show .
Préparation basée sur les réclamations (#1445)
Les tests déterministes prouvent des mécanismes ; les E2E réels prouvent des capacités. Un tas de cas déterministes réussis ne doit jamais ressembler à une préparation pour une capacité réelle qui n'a jamais été exercée. La préparation est donc calculée par réclamation de capacité déclarée, puis agrégée — pas à partir d'un décompte de cas.
Déclarez capability_claims (gardées séparées de la prose de périmètre de preuve claims
afin que les fixtures v2 restent valides). Chaque cas qui soutient une réclamation s'étiquette :
{
"capability_claims": [
{"id": "ask.roundtable.mixed_provider", "description": "...",
"criticality": "critical",
"evidence_required": {"deterministic": true, "live_e2e": true}}
],
"cases": [
{"name": "roundtable-live", "type": "positive", "evidence_class": "live_e2e",
"supports_claims": ["ask.roundtable.mixed_provider"],
"command": ["bash", "run.sh", "..."],
"expected": {"exit_code": 0, "artifacts": [{"path": "out/join.json", "json_pointer": "/settled", "equals": true}]}}
]
}
Classes de preuve : deterministic, property_or_fuzz,
fault_injected_deterministic, live_e2e, adversarial_live_e2e,
human_evaluation. Verdicts par réclamation : PROVEN, PARTIALLY_PROVEN,
BLOCKED_EXTERNAL, FAILED, NOT_ESTABLISHED. Une compétence est READY uniquement lorsque
chaque réclamation critique requise est PROVEN selon ses propres exigences de preuve.
Lorsque des réclamations sont déclarées, la barrière de run est la pire des préparations
basée sur les cas et basée sur les réclamations, donc vingt cas déterministes verts avec un
emplacement live_e2e requis non satisfait ne peuvent pas atteindre READY. Un cas réel soutenant
plus d'une réclamation doit porter des artefacts indépendants par réclamation, sinon il ne compte
pour aucune d'elles. report["capability_readiness"] contient la ventilation par réclamation.
Réclamations dérivées d'invariants (ne jamais simuler de réponse) — opérateur 2026-09-11
L'essai oai a été perdu à cause d'une omission : l'exigence "aucune PII n'atteint la sortie" a été silencieusement réduite à "aucune chaîne PII n'atteint la sortie." Chaque évaluation, vérificateur et exécution de qualification a prouvé la réclamation réduite verte tandis que les numéros de téléphone de type entier passaient sans être touchés. Le client a lu l'invariant au niveau du domaine ; nous l'avons prouvé au niveau du mécanisme. RÈGLES :
- Les réclamations sont dérivées de l'invariant du domaine, pas de ce que le code
fait déjà. Avant de déclarer
capability_claims, écrivez l'invariant dans les termes du client ("aucun X n'atteint la sortie dans TOUTE représentation"), puis déclarez-le — textuellement — comme la réclamation critique. Une réclamation qui décrit le comportement actuel est un simulacre. - La matrice de représentation est obligatoire pour les réclamations portant sur des valeurs. Toute réclamation concernant des valeurs de données (PII, argent, identifiants, secrets) doit énumérer les représentations que le format d'entrée admet — chaîne JSON/entier/flottant/notation scientifique, citation CSV, types SQL — et comporter au moins un cas adversarial par représentation, chacun capable d'échouer. Une réclamation de valeur sans matrice de représentation ne peut pas être PROVEN.
- Une jointure de couverture nommée
value-representation(criticalité : critique) est requise pour tout pipeline qui transforme, filtre ou masque des valeurs de données. L'audit doit montrer une barrière capable à cette jointure. - Les vérificateurs indépendants ne doivent pas partager le générateur de données du producteur. Les fixtures du vérificateur doivent inclure des représentations que les fixtures de la transformation n'incluent pas, sinon l'indépendance est nominale.
- Lorsqu'un écart de représentation est livré, enregistrez-le dans
fixtures/regressions.json(incident → garde conservée) et dans la jointurevalue-representation— la même classe d'omission doit être détectable pour toujours.
Le contrat anti-slush rejette la plomberie auto-satisfaisante ; ces règles rejettent les hypothèses auto-satisfaisantes — la suite qui est honnête, adversarial, multi-essais, et pourtant aveugle parce que chaque fixture partage une précondition invisible sur la forme des données.
Contrat de preuve de bout en bout réel (#1446)
evidence_class n'est pas déduit d'une commande contenant run.sh ou curl.
Un cas qui déclare une classe réelle (live_e2e/adversarial_live_e2e) est
qualifié structurellement et déclassé (jamais accepté silencieusement) s'il :
- s'alimente lui-même avec des entrées
fixtures//stub/mockedcomme autorité limite ; - n'atteint pas un point d'entrée de production substantiel ;
- n'a pas d'oracle de relecture indépendant (
expected.artifacts,readback: true, oustdout_excludes) — un code de sortie plus la prose de succès de la commande n'est pas une preuve ; ou - revendique une preuve réelle pour les gestionnaires
$askdu navigateurtau-dag/competesans--executeet une relecture à partir des artefacts d'exécution du navigateur/fournisseur tels quenode-receipt.json,response.md,response.meta.json,browser-tab-lifecycle.json,execution-status.json,roundtable-summary.md, oucompete-scorecard.json. Les cas$askde compilation/pré-vérification sont une preuve de mécanisme déterministe, jamaislive_e2e.
Un cas déclassé devient fault_injected_deterministic (défaut sur un chemin réel)
ou deterministic, et le rapport enregistre evidence_disqualifiers. Un emplacement
réel requis rencontré uniquement par un cas BLOCKED_EXTERNAL laisse la réclamation non-PROVEN.
Les exemptions sont explicites et limitées dans le temps. Une réclamation peut porter des exemptions nommant
evidence_class, reason_code, justification, owner, et expires. Une
exemption valide est affichée dans le rapport (exempt_evidence) mais ne rend jamais une
réclamation PROVEN ; une exemption expirée ou incomplète est ignorée. Aucune exemption globale
eval_not_required ne satisfait une capacité opérationnelle exécutable.
Incident → régression conservée (#1447)
Un échec en production devient une preuve permanente dans fixtures/regressions.json
(agentic_evals.regressions.v1). Chaque enregistrement lie l'incident aux réclamations
et aux jointures qu'il a menacées, nomme le cas de garde conservé (retained_case /
retained_fixture), et comporte une fail_before_fix.proof_command.
regressions show <skill>/regressions audit <root>rapporte : les régressions non protégées (cas conservé manquant/renommé), les régressions jamais prouvées (peut-être vides), les preuves réelles obsolètes, les régressions retirées, et les incidents ouverts sans mappage de régression.regressions verify <skill>réexécute chaque commande de preuve et confirme que la garde échoue réellement contre le comportement cassé (sort non nul) — une preuve de non-vacuité, pas un indicateur auto-déclaré. Il se termine avec un code non nul si une régression active dont la garde ne peut pas démontrer l'échec-avant-correction.
Corriger un bogue en modifiant l'attente de l'évaluation pour qu'elle corresponde à la sortie cassée est exactement
ce que la preuve de non-vacuité détecte. Retirer une régression nécessite une raison retirement.
Suffisance de couverture basée sur les risques (#1448)
Compter les cas est manipulable. coverage audit/coverage show demande, par seam
déclarée : y a-t-il au moins un cas capable de détecter une régression à cette
seam, dans chaque classe de preuve que le risque de la seam exige ? Déclarez seams avec
seam_id, seam_type, criticality, et required_evidence ; étiquetez les cas avec
seams. Un cas couvre une seam uniquement lorsqu'il liste cette seam et comporte un
oracle qui peut échouer — un simple exit 0 positif est weak_only et ne compte
pas. L'audit rapporte les jointures couvertes/non couvertes, la couverture faible uniquement, la couverture réelle vs
la couverture déterministe, les régressions d'incidents mappées et les prochaines évaluations priorisées.
La couverture (une garde capable est déclarée) et l'actualité (une preuve réelle est
actuelle) sont des dimensions séparées ; l'exécuteur n'exécute pas les cas de seam ici.
Contrat de fixture
{
"version": 2,
"skill": "example-skill",
"trials": 3,
"proof_scope": "fixture wiring smoke",
"claims": {
"proves": "the declared command exits with the expected status",
"does_not_prove": "semantic correctness, live service behavior, or full skill readiness"
},
"cases": [
{
"name": "happy-path",
"type": "positive",
"command": ["echo", "success"],
"expected": {
"exit_code": 0,
"stdout_contains": ["success"]
}
}
]
}
Chaque cas doit déclarer :
nametype:positive,negative, ouadversarialcommand: une liste argv non videexpected.exit_code
Attentes optionnelles :
expected.stdout_containsexpected.stderr_containsmust_exercise: liste de sous-chaînes de commandes de contrat aval qui doivent apparaître dans la commande de haut niveau du cas. Utilisez cela lorsqu'un wrapper pourrait autrement simuler le succès ; par exemple, une évaluation qui revendique la routabilité$ticket/$project-watchdogdoit inclure"skills/ticket/run.sh feature", pas seulement un script d'aide qui imprimeOK.
Contrat anti-slush (échec-fermé)
Une fixture d'évaluation de compétence est REJETÉE au moment du chargement (sortie non nulle, pas d'exécution) si elle est une plomberie déterministe auto-satisfaisante plutôt qu'une preuve réelle. Pour passer, une fixture de compétence DOIT :
- définir
trials>= 2 (un seul essai n'est pas une preuve) ; - inclure au moins un cas
negativeouadversarial(une fixture entièrement positive est auto-satisfaisante) ; - inclure au moins un cas réel :
"real_world": truedont la commande exerce un chemin réel (run.shde la compétence / un script / HTTP réel / un exécuteur de test) et ne s'alimente PAS avec des entrées facticesfixtures/; - ne contenir aucun cas trivial
echo/constant qui ne prouve rien.
Le message de rejet nomme chaque violation. Cela empêche une évaluation qui réussit trivialement tout en ne prouvant rien sur le fonctionnement réel de la compétence.
Niveau de conformité ("eval_tier": "compliance")
Une fixture qui protège une étape de pipeline de conformité déclare
"eval_tier": "compliance" et l'exécuteur APPLIQUE ALORS le contrat strict en plus du
contrat de base (directive de l'opérateur 2026-08-12, "c'est un pipeline de conformité
et doit être robustement durci"). Une telle fixture est REJETÉE sauf si :
- une majorité stricte des cas sont
adversarial/negative(plus de la moitié, pas exactement la moitié — les contrôles positifs sont la minorité) ; - au moins un cas est non déterministe : sa commande échantillonne de nouvelles entrées
à chaque exécution via
--samples,--seed, ou un shell$RANDOM(un nom de script de sonde avec une clé fixe ne compte pas) ; - chaque cas non déterministe nomme
--samples>= 50, de sorte que la couverture de chaque étape soit de centaines à des milliers d'assertions par exécution, ciblant ~1000 par étape sur ses modes.
La déclaration ne peut pas être silencieusement assouplie : les fixtures du pipeline de conformité
lui-même définissent le niveau, donc le supprimer pour contourner la barrière est en soi une régression.
tests/test_compliance_tier_gate.py épingle chaque règle contre son affaiblissement.
Deux exemptions déclarées honnêtement contournent la barrière — jamais valables pour une véritable évaluation de compétence :
"eval_kind": "runner_selftest"— une fixture qui teste cet exécuteur lui-même ou est un exemple de documentation."eval_kind": "scaffold"— la fixture mécanique de première posture émise parscaffold-fixture/apply-scaffolds, que l'audit signale toujours comme nécessitant des cas réels.
Mappage de la préparation
Le score de préparation est calculé sur les cas obligatoires uniquement ("required": false retire un
cas). Un cas BLOCKED obligatoire ne peut pas atteindre READY : une précondition non satisfaite est
une absence de preuve, pas une preuve de succès.
READY: chaque cas obligatoire a réussi chaque essai.USABLE_WITH_GAPS: au moins un essai a réussi et au moins un n'a pas réussi.NOT_READY: les essais ont été exécutés mais aucun cas obligatoire n'a été entièrement réussi.NOT_ESTABLISHED: aucun cas n'a été exécuté.
Sortie échec-fermée
run se termine avec un code non nul sauf si la préparation est READY. Un exécuteur qui se termine
avec le code 0 sur USABLE_WITH_GAPS permet à un travail CI externe de devenir vert malgré des cas
échoués, ce qui est exactement l'échec que cette barrière est conçue pour empêcher. Utilisez --report-only lorsque vous voulez le
rapport sans la barrière.
Résultats des cas
Chaque cas rapporte un outcome, car ces éléments ont des significations différentes pour une barrière :
| Résultat | Signification |
|---|---|
PASS | chaque essai a satisfait toutes les attentes |
FAIL | un défaut, un délai d'attente dépassé, ou un essai qui a laissé un processus enfant en cours |
BLOCKED | une précondition n'était pas satisfaite ; déclarez les marqueurs via blocked_when_stdout_contains |
NOT_TESTED | aucun essai n'a été exécuté |
Assertions sur les artefacts
La correspondance de sous-chaîne stdout ne peut pas exprimer "ces deux reçus nomment la même
session" ou "l'exécution n'a rien laissé derrière". Déclarez expected.artifacts :
"expected": {
"exit_code": 0,
"artifacts": [
{"path": "out/session.json", "json_pointer": "/sessionId",
"equals_artifact": {"path": "out/detach.json", "json_pointer": "/sessionId"}},
{"path": "out/attach.json", "json_pointer": "/managed", "is_true": true},
{"path": "out/cleanup.log", "lines_contain": ["cleanup complete"]},
{"path": "out/result.json", "json_pointer": "/data", "is_type": "array",
"min_length": 1},
{"path": "out/status", "content_is_empty": false}
]
}
Les assertions d'artefacts incluent json_pointer, equals_artifact (valeur égale entre artefacts),
is_true, lines_contain, is_type, min_length, content_is_empty. Chaque assertion est
indépendante ; une seule assertion échouée vaut pour l'ensemble du cas. Les artefacts peuvent
être marqués optional (l'assertion est ignorée si le fichier n'existe pas).
Cas multi-essais avec piège de type de donnée
Un piège coercitif contre les hypothèses d'entrée implicites :
{
"name": "type-coercion-trap",
"type": "adversarial",
"real_world": true,
"command": ["bash", "run.sh", "filter", "--input", "data/phone-types.json"],
"artifacts": [
{"path": "input/phone-types.json", "content": "[\"555-0100\", 5550101, 5550102.0, 5.550103e6, \"+1 (555) 010-3\", \"555-0104\"]"}
],
"expected": {
"exit_code": 0,
"artifacts": [
{"path": "output/risk-assessment.json", "json_pointer": "/phone_numbers/0/highest_risk", "matches_regex": "REJECTED|REDACTED|FLAGGED"}
]
}
}
Chaque artefact d'entrée JSON est écrit dans un fichier avant l'exécution de la commande (mécanisme d'input-files).
C'est le seul mécanisme pour introduire des entrées réelles sans facticité auto-alimentée.
Chemins de commande échoués (sortie non nulle)
Un cas peut s'attendre à un échec et l'attesté :
{
"name": "invalid-config-rejected",
"type": "negative",
"command": ["bash", "run.sh", "--config", "nonexistent.conf"],
"expected": {
"exit_code": 1,
"stderr_contains": ["config not found"]
}
}
Sortie en bloc de coverage audit
{
"version": 1,
"skill": "some-skill",
"coverage": {
"seams": [
{"seam_id": "value-representation",
"seam_type": "transform",
"criticality": "critical",
"required_evidence": ["fault_injected_deterministic", "live_e2e"],
"covered": {},
"weak_only": ["deterministic"],
"uncovered": ["fault_injected_deterministic", "live_e2e"],
"regressions_mapped": ["r-2026-phone-type-coercion"]},
{"seam_id": "value-representation",
"seam_type": "router",
"weak_only": [{"case": "smoke", "class": "deterministic"}],
"covered": {"deterministic": {"case": "smoke", "class": "deterministic", "oracle": true}}}
],
"uncovered_seams": [],
"weak_only_seams": [],
"live_coverage": {}, // pass en cours
"deterministic_coverage": {},
"findings": [
{"severity": "high",
"finding": "la jointure value-representation manque une preuve fault_injected et live_e2e ; la régression r-2026-phone-type-coercion est mappée => priorité"}
],
"prioritized_next_evals": ["fault_injected_deterministic", "live_e2e"]
}
}
Sortie en bloc de regressions audit
{
"version": 1,
"skill": "some-skill",
"regressions": {
"value-representation": {
"r-2026-phone-type-coercion": {
"incident_id": "inc-2026-phone-type-coercion",
"status": "active",
"retained_case": "type-coercion-trap",
"retained_fixture": "fixtures/agentic_eval.json",
"fail_before_fix_proof_command": ["bash", "run.sh", "run", "fixtures/agentic_eval.json", "--case", "type-coercion-trap"],
"source": ["incident", "operator_directive"],
"claims_supported": ["ask.roundtable.mixed_provider"],
"seams": ["value-representation"],
"detection_date": "2026-09-11",
"fix_date": "2026-09-12"
}
},
"unprotected": [],
"never_proven": [],
"stale_live_proof": [],
"retired": [],
"open_incidents": []
},
"non_vacuity": {
"proof_command_results": [
{"command": ["bash", "run.sh", "run", "fixtures/agentic_eval.json", "--case", "type-coercion-trap"],
"exit_code": 0, // code de sortie du runner pour l'échec attendu
"evidence_of_failure": "exit_code=1"}
]
},
"findings": [
{"severity": "high",
"finding": "r-2026-phone-type-coercion vérifié comme non-vide ; la garde échoue avant correction"}
]
}
Format du rapport
{
"version": 2,
"skill": "example-skill",
"timestamp": "2026-09-11T12:00:00Z",
"trials": 3,
"readiness": "USABLE_WITH_GAPS",
"summary": {
"total": 4,
"passed": 3,
"failed": 1,
"blocked": 0,
"not_tested": 0,
"required": {"passed": 3, "failed": 1, "blocked": 0, "not_tested": 0}
},
"cases": [
{"name": "happy-path", "type": "positive", "required": true, "outcome": "PASS", "trials": [{"trial": 1, "exit_code": 0, "duration_ms": 45}, {"trial": 2, "exit_code": 0, "duration_ms": 42}, {"trial": 3, "exit_code": 0, "duration_ms": 41}]},
{"name": "type-coercion-trap", "type": "adversarial", "required": true, "outcome": "PASS", "trials": [{"trial": 1, "exit_code": 1, "duration_ms": 120}]},
{"name": "broken-case", "type": "positive", "required": true, "outcome": "FAIL", "trials": [{"trial": 1, "exit_code": 1, "duration_ms": 85}]}
],
"capability_readiness": {
"ask.roundtable.mixed_provider": {
"criticality": "critical",
"verdict": "PARTIALLY_PROVEN",
"evidence_required": {"deterministic": true, "live_e2e": true},
"evidence_provided": {"deterministic": true, "live_e2e": false},
"supporting_cases": {"deterministic": ["roundtable-live"], "live_e2e": []},
"blocking_cases": [],
"evidence_disqualifiers": null,
"exempt_evidence": {}
}
},
"evidence_disqualifiers": null
}
Une sortie échouée ressemble à :
{
"readiness": "USABLE_WITH_GAPS",
"case_outcomes": [{"name": "happy-path", "outcome": "PASS"}, {"name": "broken-case", "outcome": "FAIL"}]
}