GitHub

Know if your agent got worse before you merge.

holdline runs your agent's eval cases several times, checks what it did as well as what it said, measures your LLM judge against people, and fails the pull request when a change makes the agent measurably worse.

Check your LLM judge Open a live report
pip install holdline
helpdesk-gate.html
A holdline report: the gate passed, pass rate 96.1 percent, pass^3 95.6 percent, a case map of 68 cases with two failing and one flaky, and a list of the cases that need attention.

A real report: helpdesk-agent with one answer sample, gated against its five-sample production setting. Open it and click any case to see its trace.

Most agent evals tell you less than you think.

Four ways a change slips through, and what holdline does about each.

Only the final answer is checked

An agent can reach the right answer the wrong way: skip the policy check, call a tool it must never call, or loop.

Check the path too

Required, forbidden and ordered tools, step budgets, loop detection and tool arguments validated against a schema.

Each case runs once

An agent that passes four times in five looks perfect today and broken tomorrow.

Run every case k times

pass^k, the chance all k runs pass, is the number that matters when the agent runs unattended. Flaky cases are named.

Nobody measured the judge

An LLM judge can be lenient exactly where it matters, and an average can hide a judge that is lenient on one kind of case and harsh on another.

Calibrate it against people first

Agreement beyond chance, false passes, and bias per group. A judge that is not fit is not allowed to grade.

"82% to 79%" might be noise

Two pass rates side by side cannot tell a regression from a bad day.

Compare the same cases, with statistics

Cases are paired before and after. An exact McNemar test on the ones that flipped, an interval on the change, and must-pass cases that never get a pass on statistics.

Start with what you already have.

No new framework and no rewrite. Two ways in, each a minute or less.

Already have eval results from another tool?
# CSV, JSON or JSONL: a case id and pass/fail (or a score)
pip install holdline
holdline compare before.csv after.csv --html report.html
# paired statistics on the same cases, the cases that broke,
# pass^k when you have repeats; exits 1 on a real regression
Grading with an LLM judge?
# drop human and judge verdicts into the judge check
# kappa, false passes, bias by group, in your browser
aashanjaved.com/holdline/judge

# or in CI
holdline calibrate --labels labels.jsonl --strict

Three steps, each a plain file you can open.

holdline architecture: trust the judge, evaluate, decide.

1Trust the judge

Once, before an LLM judge grades anything, compare it with human labels on the same items.

2Evaluate

Run each case k times, record a trace of every tool and model call, grade each run, save run.json.

3Decide

In CI, compare this run with the one from main, case by case, and pass or fail the pull request with the reasons.

Proven on a real agent.

holdline over helpdesk-agent, a support agent that answers only from its help centre and escalates the rest: 68 labelled tickets, three runs each, gemini-2.5-flash, on 21 October 2026.

5 samples1 sample
Pass rate95.6%96.1%
pass^395.6%95.6%
Escalated uncovered tickets100%100%
Answered covered tickets97.8%97.8%
Model calls per run4.41.69

Gate passed. 0 of 68 cases broke and none were fixed. The cheaper setting is not measurably worse on this suite, at 62% fewer model calls.

What the suite cannot see: escalation here comes from the agent's decide step, so the extra samples are never stressed. Borderline tickets come before shipping that change.

  • A covered ticket was escalated"What is the member limit on the Free plan?" went to a human in all three runs. A real over-refusal.outcome: expected answer, got escalate
  • The right answer, citing the wrong articleSCIM on the Business plan was answered correctly from the pricing article instead of the single sign-on one. An answer-only eval passes it.cites_gold_article: cited passage 11, gold article single-sign-on
  • The grader was wrong, not the agent"Only the Owner can transfer ownership of a workspace." failed a token-overlap rule against the gold "the Owner". The reason to measure graders before trusting them.helpdesk_correct: answered 'Only the Owner can...', gold 'the Owner'
helpdesk-gate.html#/cases/what-is-the-member-limit-on-the
The case view: a list of 68 cases on the left, and on the right the failing Free-plan case with its input, expected outcome, failed checks and the trace of its search and decide steps.

Measure the judge before it grades your releases.

  • Agreement beyond chance. Cohen's kappa with an interval, because raw agreement looks high whenever most outputs pass.
  • False passes first. A judge that passes what a person failed lets a regression ship.
  • Bias per group. Here the overall bias is +0.01, which looks harmless, while the judge is lenient on short answers and harsh on multi-step ones. One threshold cannot fix that.
  • Synthetic labels, made to show the failure mode. Open the report.
judge-calibration.html
A calibration report: not fit to gate releases, kappa 0.75 with a lower bound under 0.6, an 18.5 percent false-pass rate, and a bias chart where one group is lenient and two are harsh.

A decorator in your agent, one step in CI.

Record what the agent does
import holdline

@holdline.tool
def search(query: str) -> list: ...

def agent(question: str) -> dict:
    hits = search(query=question)
    holdline.step("llm", "answer", question, hits)
    return {"outcome": "answer", "answer": ...}
# outside a holdline run both are no-ops
Gate every pull request
# .github/workflows/agent-evals.yml
- uses: Aashan47/holdline@v0.1.0
  with:
    suite: evals/cases.jsonl
    target: my_agent:agent
    baseline: .holdline/baseline.json
    repeats: "3"
    max-drop: "0.05"
# FAIL blocks the merge; the verdict lands in the job summary
Zero dependenciesStandard library only, Python 3.9 and up. Installs into any agent's environment.
Any agentA target is any Python function, sync or async. No framework lock-in.
Plain filesCases, runs and labels are JSON lines that diff cleanly in a pull request.
One-file reportsSelf-contained HTML that opens anywhere: a CI artifact, an email, an offline laptop.
OpenTelemetry exportTraces export as OTLP JSON with the GenAI attribute names.
Open sourceMIT licensed, tested in CI on Python 3.9, 3.11 and 3.13.

Try it on the example agent in two minutes.

A toy refund agent ships with the repo, in a careful version and a "helpful" one that refunds whenever asked. Watch the gate catch it.

Read the quickstart
# in a clone of the repo
cd examples
holdline run  --suite refund_cases.jsonl --target refund_agent:agent_v1 -k 3 --out v1.json
holdline run  --suite refund_cases.jsonl --target refund_agent:agent_v2 -k 3 --out v2.json
holdline gate --baseline v1.json --candidate v2.json --html report.html
# exits 1: a must-pass case broke