Summary
A small, opt-in reporter — agentdiff.reporters.openeval — that renders a DiffReport as an EvalPort ResultSet (JSON), so a CI regression-gate run from AgentDiff can land in the same results pipeline/dashboard as evals from other frameworks. No new dependency, no change to the trace schema or the comparison engine — same shape as the existing reporters/pr.py / reporters/markdown.py / reporters/terminal.py modules, just another sink for a DiffReport.
Why I think this fits (and where it doesn't)
I read through engine/comparator.py, models/report.py, and the three existing reporters before writing this, so I want to be precise about the actual overlap, not a generic "let's integrate!" pitch.
AgentDiff's DiffReport isn't a per-input correctness grade — it's a deterministic structural diff between two trajectories (TDI via LCS alignment, WEI, loop detection, RSR, resource deltas). EvalPort's TestCase/expected_output/llm_judge grader machinery genuinely doesn't apply here, and I'm not proposing any of that. EvalPort's spec is explicit that it "does not define a trace format" and only references trace IDs — so I'm not proposing AgentDiff adopt EvalPort's AgentTrace shape either; schema/agent_trace.schema.json stays exactly as-is.
The part that does line up is the result layer, not the input layer. EvalPort's ResultSet is just: one result per evaluated unit, each carrying grader_results (id/type/score/passed/reason), rolling up to a summary. That maps cleanly onto one DiffReport if each of AgentDiff's checks (TDI, WEI, loop count, RSR, cost/latency/token delta) becomes one grader_result. The spec's grader type system is explicitly open for this: any non-standard type string is valid as long as params.handler is set (spec/SPEC.md §"Type openness (normative)"), which is exactly the "framework-native, not a fake standard grader" case — same pattern the repo's own crewai-openeval-adapter uses for gr_tool_selection (type: "custom", params.handler: "crewai:tools_subset").
Concretely, one comparison run (baseline_id vs candidate_id) becomes one results[] entry:
# agentdiff/reporters/openeval.py (sketch — not a working PR yet)
from agentdiff.models.report import DiffReport
def generate_openeval_resultset(
report: DiffReport,
*,
suite_id: str = "agentdiff",
run_id: str,
started_at: str,
completed_at: str,
) -> dict:
def grader(gid, gtype, score, passed, reason):
return {
"grader_id": gid,
"type": gtype,
"score": score,
"passed": passed,
"reason": reason,
"params": {"handler": f"agentdiff:{gtype}"}, # required for non-standard types
}
grader_results = [
grader("gr_trajectory_divergence", "agentdiff_tdi",
1.0 - report.trajectory_divergence_index,
report.trajectory_divergence_index <= 0.3, # DEFAULT_MAX_DIVERGENCE in reporters/pr.py
f"TDI={report.trajectory_divergence_index:.4f}"),
grader("gr_wasted_effort", "agentdiff_wei",
1.0 - report.candidate_wei, report.candidate_wei <= report.baseline_wei,
f"candidate WEI={report.candidate_wei:.4f} (baseline {report.baseline_wei:.4f})"),
grader("gr_loop_detection", "agentdiff_loops",
0.0 if report.loops_detected else 1.0, not report.loops_detected,
f"{len(report.loops_detected)} loop(s) detected"),
grader("gr_recovery_ratio", "agentdiff_rsr",
None, report.recovery_step_ratio <= 1.5,
f"RSR={report.recovery_step_ratio:.2f} "
f"({report.candidate_recovery_steps}/{report.baseline_recovery_steps})"),
grader("gr_resource_delta", "agentdiff_resource_delta", None,
report.cost_delta_percentage <= 10.0,
f"cost {report.cost_delta_percentage:+.2f}%, "
f"latency {report.latency_delta_percentage:+.2f}%, "
f"tokens {report.token_delta_percentage:+.2f}%"),
]
passed = sum(1 for g in grader_results if g["passed"])
return {
"version": "1.0.0",
"suite_id": suite_id,
"run_id": run_id,
"started_at": started_at,
"completed_at": completed_at,
"results": [{
"test_case_id": f"{report.baseline_id}__vs__{report.candidate_id}",
"grader_results": grader_results,
"passed": report.passed,
"metadata": {
"gate_provenance": report.gate_provenance,
"violations": [v.message for v in report.violations],
"warnings": [w.message for w in report.warnings],
},
}],
"summary": {
"total": len(grader_results),
"passed": passed,
"failed": len(grader_results) - passed,
"pass_rate": passed / len(grader_results),
},
}
Honest caveats
- Narrow, real value today. EvalPort's own adoption notes (
spec/ADOPTION.md) are candid that native framework support hasn't landed anywhere yet — this only helps if you're already aggregating results from multiple eval tools into one EvalPort-shaped store/dashboard. I'm not claiming demand exists inside this repo's userbase; I genuinely don't know if it does.
- One-way, and deliberately so. No
TestCase import direction — AgentDiff doesn't consume single graded inputs, so there's nothing sensible to import. This is export-only, same direction as reporters/pr.py.
- Threshold values in the sketch above are illustrative defaults, not what a real PR would hardcode — a real implementation would thread through whatever thresholds the caller already passed to
assert_no_regressions/generate_pr_markdown rather than re-deriving them.
- Zero new dependency: the sketch above is a plain dict, no
evalport-sdk import required, so it doesn't trip the "adds a dependency" review gate in CONTRIBUTING.md. It also doesn't touch schema/ at all.
Since CONTRIBUTING.md asks to open an issue first for anything that touches public API surface, I'm posting this as a proposal rather than a PR — happy to build it as feat/openeval-resultset-reporter against current main (0.5.0) if this is a direction you'd want, or to hear why it isn't.
— Sahi, independent contributor (not affiliated with this project)
Summary
A small, opt-in reporter —
agentdiff.reporters.openeval— that renders aDiffReportas an EvalPortResultSet(JSON), so a CI regression-gate run from AgentDiff can land in the same results pipeline/dashboard as evals from other frameworks. No new dependency, no change to the trace schema or the comparison engine — same shape as the existingreporters/pr.py/reporters/markdown.py/reporters/terminal.pymodules, just another sink for aDiffReport.Why I think this fits (and where it doesn't)
I read through
engine/comparator.py,models/report.py, and the three existing reporters before writing this, so I want to be precise about the actual overlap, not a generic "let's integrate!" pitch.AgentDiff's
DiffReportisn't a per-input correctness grade — it's a deterministic structural diff between two trajectories (TDI via LCS alignment, WEI, loop detection, RSR, resource deltas). EvalPort'sTestCase/expected_output/llm_judgegrader machinery genuinely doesn't apply here, and I'm not proposing any of that. EvalPort's spec is explicit that it "does not define a trace format" and only references trace IDs — so I'm not proposing AgentDiff adopt EvalPort'sAgentTraceshape either;schema/agent_trace.schema.jsonstays exactly as-is.The part that does line up is the result layer, not the input layer. EvalPort's
ResultSetis just: one result per evaluated unit, each carryinggrader_results(id/type/score/passed/reason), rolling up to asummary. That maps cleanly onto oneDiffReportif each of AgentDiff's checks (TDI, WEI, loop count, RSR, cost/latency/token delta) becomes onegrader_result. The spec's grader type system is explicitly open for this: any non-standard type string is valid as long asparams.handleris set (spec/SPEC.md§"Type openness (normative)"), which is exactly the "framework-native, not a fake standard grader" case — same pattern the repo's owncrewai-openeval-adapteruses forgr_tool_selection(type: "custom",params.handler: "crewai:tools_subset").Concretely, one comparison run (
baseline_idvscandidate_id) becomes oneresults[]entry:Honest caveats
spec/ADOPTION.md) are candid that native framework support hasn't landed anywhere yet — this only helps if you're already aggregating results from multiple eval tools into one EvalPort-shaped store/dashboard. I'm not claiming demand exists inside this repo's userbase; I genuinely don't know if it does.TestCaseimport direction — AgentDiff doesn't consume single graded inputs, so there's nothing sensible to import. This is export-only, same direction asreporters/pr.py.assert_no_regressions/generate_pr_markdownrather than re-deriving them.evalport-sdkimport required, so it doesn't trip the "adds a dependency" review gate inCONTRIBUTING.md. It also doesn't touchschema/at all.Since
CONTRIBUTING.mdasks to open an issue first for anything that touches public API surface, I'm posting this as a proposal rather than a PR — happy to build it asfeat/openeval-resultset-reporteragainst currentmain(0.5.0) if this is a direction you'd want, or to hear why it isn't.— Sahi, independent contributor (not affiliated with this project)