cd /news/large-language-models/build-the-eval-pyramid-a-starter-har… · home topics large-language-models article
[ARTICLE · art-90031] src=bharad.dev ↗ pub= topic=large-language-models verified=true sentiment=· neutral

Build the Eval Pyramid: A Starter Harness for LLM and Agent Testing

The open-source Eval Pyramid Starter, a provider-neutral TypeScript harness for testing LLM and agent systems, enables engineers to clone a repository and run offline evaluations with deterministic graders, repeated runs, model judges, and human review. The harness, created by Bharadwaj Pendyala, includes a typed task and trial contract, generates reports under reports/latest, and supports metrics like pass@1, pass@k, and pass^k. It is designed to be run entirely offline by default, with synthetic examples and no credentials required, and includes redaction for sensitive data.

read10 min views1 publishedAug 10, 2026
Build the Eval Pyramid: A Starter Harness for LLM and Agent Testing
Image: Bharad (auto-discovered)

An eval methodology becomes useful when another engineer can clone it, replace one boundary, and get trustworthy evidence without rebuilding the whole stack.

That is the goal of the open-source Eval Pyramid Starter. It is a provider-neutral TypeScript harness that runs entirely offline by default. The example system is synthetic, the reports stay local, and no credential is required. You can inspect every layer before connecting a real model or tool.

This is part three of the Eval Pyramid series. Part one explains why the layers exist. Part two explains pass@k, pass^k, and judge calibration. This article connects those ideas to code.

TLDR #

  • Keep one typed task and trial contract across deterministic graders, repeated runs, model judges, and human review.
  • Put machine-checkable requirements first: schema, outcome, tool policy, latency, turns, and cost.
  • Give every trial a fresh system instance and temporary workspace.
  • Calculate reliability per task, then average across tasks. Report pass@1

,pass@k

, andpass^k

together. - Calibrate model judges against human labels and fail closed when their output is missing or invalid.

  • Make release gates explicit, retain failure evidence, route uncertain cases to people, and run cheap checks on every pull request while running broader evals on a schedule.

Clone the runnable reference #

git clone https://github.com/bharadwaj-pendyala/eval-pyramid-starter.git
cd eval-pyramid-starter
npm ci
npm run eval:deterministic
npm run eval:repeat -- --trials 10 --k 3
npm run eval:judge
npm run eval:review

The default commands exercise three refund-support cases: an eligible refund, an out-of-window request, and a request missing its order identifier. They do not call an external model or touch a real customer system.

The generated evidence lives under reports/latest

:

summary.json          aggregate metrics and release-gate checks
trials.jsonl          one complete record per trial
failures.md           failed graders grouped by task and trial
review-queue.jsonl    uncertain or failed cases for human labels
index.html            self-contained report for local inspection

Generated reports are ignored by Git. That matters because even a local eval can capture private output. The starter escapes dynamic HTML and redacts common credential fields, email addresses, bearer tokens, and OpenAI-style keys. Those controls are defense in depth. Use synthetic or properly de-identified fixtures and replace the redactor with one suited to your data.

One contract holds the pyramid together #

Do not build four disconnected evaluation systems. Define the task once, capture the run once, and let each layer inspect the evidence it understands.

The starter's system boundary is small:

export interface SystemUnderTest {
  run(task: EvalTask, context: TrialContext): Promise<AgentRun>;
  dispose?(): Promise<void>;
}

export interface SystemUnderTestFactory {
  create(): Promise<SystemUnderTest>;
}

EvalTask

describes the input, expected state, tool rules, budgets, and optional rubric. AgentRun

records the output, final state, observable transcript, tool calls, and usage metrics. TrialContext

supplies the run identifier, trial number, and an isolated workspace.

This boundary is intentionally provider-neutral. Your adapter can call a hosted model, a local model, an HTTP service, a workflow engine, or the same application binary users call. The graders do not need to know.

Layer one: make the contract executable #

The example task file is JSONL, one versionable task per line. Reformatted for readability, one case looks like this:

{
  "id": "eligible-refund",
  "input": {
    "customerId": "customer-100",
    "message": "I was charged twice for order 42."
  },
  "expected": {
    "outcome": {
      "ticket": { "status": "resolved" },
      "refund": { "status": "processed", "amount": 49 }
    },
    "requiredTools": [
      { "name": "verify_identity" },
      { "name": "fetch_policy" },
      { "name": "process_refund", "arguments": { "amount": 49 } },
      { "name": "send_confirmation" }
    ],
    "forbiddenTools": ["issue_store_credit"],
    "maxTurns": 8,
    "maxCostUsd": 0.05,
    "maxLatencyMs": 5000
  }
}

The checked-in JSON Schema rejects malformed tasks and duplicate identifiers before a trial starts. After the run, deterministic graders check the output schema, expected final state, required and forbidden tools, turn count, cost, and latency.

These checks answer concrete questions. Did the refund reach the correct state? Did identity verification happen? Did the agent avoid an unapproved action? They are cheap, stable, and easy to debug, so they belong on every pull request.

Avoid grading private chain-of-thought. The harness records observable outputs, actions, and state transitions. Those are the artifacts a team can retain, inspect, and govern.

Layer two: repeat isolated trials #

A fresh adapter instance and temporary workspace are created for every trial. Cleanup runs even after a failure. That prevents one test's files or in-memory client state from leaking into the next.

Your adapter still owns isolation outside the process. If it writes to a database, use a disposable tenant, namespace, transaction, or mocked tool layer. If it calls a model or tool, configure request and operation timeouts. The harness grades reported latency but cannot safely terminate arbitrary external work on your behalf.

For each task, let n

be the number of trials and c

the number that passed. The starter implements the finite-sample estimators from part two:

pass@k=1 − C(n − c, k) / C(n, k)
passk=C(c, k) / C(n, k)

The runner calculates these per task before averaging. That keeps a large easy task group from drowning out a small difficult one.

Report the three perspectives together:

pass@1

is the observed first-attempt experience.pass@k

measures whether at least one ofk

sampled attempts succeeds.pass^k

measures whether allk

sampled attempts succeed.

These are sample summaries, not confidence intervals. A high-impact release gate may also need confidence bounds, failure severity, and task-specific thresholds.

Layer three: calibrate judgment before trusting it #

Some requirements are semantic. The explanation may need to cite the relevant policy, include the amount, and make the next step clear without matching one exact sentence.

The starter defines a JudgeProvider

interface and includes two implementations. The fake signal judge keeps the default workflow offline and demonstrates calibration mechanics. The optional OpenAI adapter uses the Responses API with a strict JSON Schema result. Neither adapter is part of the system-under-test contract.

Every judge returns four fields:

export interface JudgeScore {
  score: number;
  confidence: number;
  rationale: string;
  unknown: boolean;
}

unknown

is important. Insufficient evidence should not become a confident zero or a guessed pass. Invalid structured output throws an error, which the trial runner records as a failure.

The command npm run eval:judge

compares the offline judge with a human-scored golden set of strong, weak, and borderline examples. A real judge needs a larger domain-specific set, labels from people who understand the product, and periodic recalibration after any model, prompt, or rubric change.

A judge can also be prompt-injected by the output it grades. Keep the rubric focused, delimit untrusted material, require structured output, and retain human review for disagreement and high-impact cases. Never make a model judge the only control over an irreversible action.

The human layer is a queue, not a ceremony #

Failed trials and grades marked unknown flow into review-queue.jsonl

. A reviewer can add a human score and notes without reconstructing the run from CI logs.

This creates a useful loop:

  • Review the failed or uncertain evidence.
  • Decide whether the system, task, deterministic grader, or model judge was wrong.
  • Turn repeated unambiguous failures into deterministic checks.
  • Add ambiguous examples and human labels to the judge's golden set.
  • Version the task, grader, prompt, model, and threshold change together.

The queue is intentionally a file in this starter. A team can later route the same typed records into an annotation tool, issue tracker, or internal review application without changing how trials are graded.

Release gates should fail for reasons you can inspect #

eval.config.ts

makes the example thresholds explicit:

thresholds: {
  deterministicPassRate: 1,
  passAt1: 0.8,
  passPowerK: 0.5,
  judgeAverage: 0.8,
}

npm run eval:ci

exits unsuccessfully when the release gate misses one of them. The JSON summary records every check with its actual value, required value, and pass status.

Do not copy these numbers into production. Establish a reviewed baseline, choose k

from actual product behavior, and set thresholds from impact and reversibility. Add zero-tolerance gates for prohibited actions. Averages should never excuse one severe policy or safety failure.

Put each layer on the right clock #

The repository includes two GitHub Actions workflows with read-only repository permissions.

The pull-request workflow installs from the lockfile, checks formatting and strict types, runs lint and coverage, builds the project, audits production dependencies, generates a report, and checks it in desktop and mobile Chromium.

The scheduled workflow runs the release-gated repeated suite each week and preserves the evidence as a short-lived artifact. A production program usually adds three clocks:

  • Pull request: fast deterministic regressions and a small smoke sample.
  • Scheduled: broader repeated trials and judge calibration, often against several model or prompt versions.
  • Production: sampled, privacy-reviewed outcomes and incidents that reveal cases the offline set missed.

Pin models and record prompts, tools, retrieval inputs, and application versions when you need comparisons across time. Otherwise a score change can arrive without a code change and leave no useful explanation.

Replace one boundary, then grow from failures #

The practical migration path is short:

  • Fork or generate a repository from the starter. - Replace the synthetic fixtures with representative, safe cases from your product's error taxonomy.
  • Implement SystemUnderTestFactory

so each trial gets a fresh client and disposable resources. - Return observable outcomes, tool calls, costs, latency, and turn counts from the adapter.

  • Add deterministic checks for every requirement code can express faithfully.
  • Add one focused judge criterion only where rules run out, then calibrate it against human labels.
  • Run a baseline, inspect every failure, and set reviewed release thresholds.

Start small enough that a person can read the whole task set. Twenty well-chosen cases with failures you understand are more useful than thousands of synthetic prompts nobody reviews. Add new cases when production, red teaming, or manual testing finds a gap.

What this starter does not claim #

The offline adapter is deterministic unless you ask it to inject a known failure. It proves the harness works. It does not characterize a real model.

The fake judge matches explicit text signals. It demonstrates the calibration contract. It is not a semantic evaluator.

The finite reliability metrics summarize observed samples. They do not prove independence, causality, or statistical certainty.

The redactor catches common patterns. It cannot guarantee arbitrary private data is gone.

The harness is a foundation, not a compliance system. High-impact domains still need threat modeling, least-privilege tool access, domain experts, incident review, and controls outside the eval process.

Those limitations are documented in the repository because a starter should teach its boundaries as clearly as its features.

The implementation is part of the argument #

An eval program earns trust when its tasks are readable, its metrics match the product, its graders are tested, its failures retain evidence, and its release rules are visible in code.

The Eval Pyramid Starter gives you that base without choosing your model provider or annotation platform. Clone it, replace the adapter, and let real failures tell you which layer to strengthen next.

Glossary #

System under test. The application or workflow being evaluated behind a provider-neutral adapter.Trial context. The run identifier, trial number, and isolated workspace supplied to one attempt.Release gate. Explicit metric thresholds that determine whether an eval command succeeds.Judge calibration. Comparison of model-judge scores with trusted human labels.Review queue. Failed or uncertain trials retained for a human decision.Golden set. Versioned examples with trusted labels used to test a judge.

References and further reading #

Eval Pyramid Starter. The complete TypeScript implementation, tests, workflows, security notes, and adaptation guide used in this article.Anthropic: Demystifying evals for AI agents. Task design, repeated trials, isolated environments, graders, and agent-eval practices.OpenAI API: Graders. Grader types, human-label comparison, and grader-hacking risks.JSON Schema: Getting started. The validation standard used for versioned task and structured judge contracts.Vitest: Testing in practice. Test organization and confidence-focused testing guidance.Playwright: Fixtures. Isolation and reusable setup for browser-level checks.Evaluating Large Language Models Trained on Code. The HumanEval paper and finite-samplepass@k

estimator.. Final-state evaluation and theτ

-bench: A Benchmark for Tool-Agent-User Interaction in Real-World Domainspass^k

reliability metric.

── more in #large-language-models 4 stories · sorted by recency
── more on @eval pyramid starter 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/build-the-eval-pyram…] indexed:0 read:10min 2026-08-10 ·