eval

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package eval is the evaluation harness for the agent loop (HP-11): it turns a stochastic, multi-turn agent into MEASURABLE data — a pass-rate distribution, a cost, a convergence curve, and an honesty number — so a change to the context policy, the prompt, or the toolset can be shown to help instead of merely felt to.

It is the consumer the agent package was shaped for: Run/RunNative already emit a typed RunResult (Outcome + Answer + Step trace + Usage) and take a silent Observer, and RunResult.Root already travels with the result as "the fixture hook". This package supplies the four things the dogfood bake-offs (docs/findings/DOGFOOD.md R9/R10) proved a serious eval needs and manual dogfooding lacked:

  1. An INDEPENDENT oracle — grading is a separate judgment from the agent's self-reported Outcome. Models reported `answered` with the build red; "did it say done" and "is it actually done" are different questions and only the Oracle answers the second.
  2. HELD-OUT cases — the grader runs inputs the model never saw, so a parser that passes the handed suite but silently accepts "2+3$" is still failed.
  3. N runs per cell — a single trial measures the dice (R9→R10 flipped the same model pass↔fail), so the unit of measurement is a distribution, not a bool.
  4. Pristine fixtures — every Trial starts from a byte-identical copy in a fresh temp dir, never the live repo and never a dir a prior trial mutated.

This file holds the case + fixture vocabulary; oracle.go grades, trial.go runs one (Case × Model) execution, and report.go aggregates and archives.

Index

Constants

View Source
const ReportSchemaVersion = "6"

ReportSchemaVersion is the current eval-report schema. v2 added LatencyMs on Trial and the reason/latency columns; v3 added Trial.NoAttempt (Grade. NoAttempt) and the best-of-N selection section; v4 added infra tracking and plan-stage cost; v5 added ladder metadata and escalation guard metrics; v6 added task-steer arm metadata. Bump on any further shape change.

Variables

View Source
var Pricing = map[string]Price{

	"openai/gpt-5.5":                {InPerM: 5.00, OutPerM: 30.00, CacheReadPerM: 0.50},
	"openai/gpt-5.6-sol":            {InPerM: 5.00, OutPerM: 30.00, CacheReadPerM: 0.50},
	"openai/gpt-5.6-terra":          {InPerM: 2.50, OutPerM: 15.00, CacheReadPerM: 0.25},
	"openai/gpt-5.6-luna":           {InPerM: 1.00, OutPerM: 6.00, CacheReadPerM: 0.10},
	"openai/gpt-5.6-sol-pro":        {InPerM: 5.00, OutPerM: 30.00, CacheReadPerM: 0.50},
	"openai/gpt-5.6-terra-pro":      {InPerM: 2.50, OutPerM: 15.00, CacheReadPerM: 0.25},
	"openai/gpt-5.6-luna-pro":       {InPerM: 1.00, OutPerM: 6.00, CacheReadPerM: 0.10},
	"anthropic/claude-fable-5":      {InPerM: 10.00, OutPerM: 50.00, CacheReadPerM: 1.00},
	"qwen/qwen3.7-max":              {InPerM: 1.20, OutPerM: 6.00},
	"anthropic/claude-opus-4.8":     {InPerM: 5.00, OutPerM: 25.00, CacheReadPerM: 0.50},
	"google/gemini-3.1-pro-preview": {InPerM: 2.00, OutPerM: 12.00, CacheReadPerM: 0.20},

	"claude-opus-4-8":  {InPerM: 5.00, OutPerM: 25.00, CacheReadPerM: 0.50},
	"claude-opus-4-7":  {InPerM: 5.00, OutPerM: 25.00, CacheReadPerM: 0.50},
	"claude-sonnet-5":  {InPerM: 3.00, OutPerM: 15.00},
	"claude-haiku-4-5": {InPerM: 1.00, OutPerM: 5.00, CacheReadPerM: 0.10},

	"deepseek/deepseek-v4-flash":   {InPerM: 0.09, OutPerM: 0.18, CacheReadPerM: 0.018},
	"tencent/hy3-preview":          {InPerM: 0.30, OutPerM: 1.20},
	"google/gemini-2.5-flash-lite": {InPerM: 0.10, OutPerM: 0.40},

	"anthropic/claude-opus-4.7":     {InPerM: 5.00, OutPerM: 25.00, CacheReadPerM: 0.50},
	"moonshotai/kimi-k2.6":          {InPerM: 0.60, OutPerM: 2.50},
	"moonshotai/kimi-k2.6:free":     {InPerM: 0.00, OutPerM: 0.00},
	"google/gemini-3-flash-preview": {InPerM: 0.50, OutPerM: 3.00, CacheReadPerM: 0.05},

	"x-ai/grok-4.3":               {InPerM: 1.25, OutPerM: 2.50},
	"x-ai/grok-4.20":              {InPerM: 1.25, OutPerM: 2.50},
	"z-ai/glm-5":                  {InPerM: 0.60, OutPerM: 1.92, CacheReadPerM: 0.12},
	"z-ai/glm-5.2":                {InPerM: 0.909, OutPerM: 2.856, CacheReadPerM: 0.169},
	"openai/gpt-5.4":              {InPerM: 2.50, OutPerM: 15.00},
	"openai/gpt-5.2-codex":        {InPerM: 1.75, OutPerM: 14.00},
	"anthropic/claude-sonnet-4.6": {InPerM: 3.00, OutPerM: 15.00},
	"claude-fable-5":              {InPerM: 10.00, OutPerM: 50.00, CacheReadPerM: 1.00},
	"qwen/qwen3-coder":            {InPerM: 0.22, OutPerM: 1.80},
	"minimax/minimax-m3":          {InPerM: 0.30, OutPerM: 1.20},
	"google/gemini-3.5-flash":     {InPerM: 1.50, OutPerM: 9.00},
	"moonshotai/kimi-k2-thinking": {InPerM: 0.60, OutPerM: 2.50},
	"deepseek/deepseek-v4-pro":    {InPerM: 0.435, OutPerM: 0.87, CacheReadPerM: 0.0036},
}

Pricing is the hand-maintained price table for the eval roster, USD per 1M tokens, in/out + cache-read refreshed from live OpenRouter 2026-07-05. It is a STATIC, in-repo table by design, not a live fetch: a report is a git-pinned, reproducible artifact (see report.go's Manifest), so the cost column must be pinned too — a price that moved after a run must not silently rewrite a past report's numbers. Update this table when the roster or its prices change; a live source can later drop in behind CostOf without touching its callers.

A slug absent here renders "—" (unknown), never $0 — free and unpriced are different facts, and conflating them would make an un-priced model look like the cheapest in the table.

Functions

func AggregateReviewFates

func AggregateReviewFates(recs []agent.RunRecord) map[string]*ReviewFateStats

AggregateReviewFates folds every record's review report into per-reviewer stats. Records without a report (gate off) are skipped; a report without a reviewer model aggregates under "(unknown)".

func CostOf

func CostOf(model string, u llm.Usage) (cost float64, ok bool)

CostOf returns the USD cost for a model's usage and whether the model was priced at all. ok=false means the slug is absent from Pricing — the caller renders "—", not 0, because free and unknown are different facts.

func RenderReviewFates

func RenderReviewFates(stats map[string]*ReviewFateStats) string

RenderReviewFates renders the aggregate as a stable, scannable table (one line per reviewer, name-sorted) for reports and the CLI.

func SelectBest

func SelectBest(trials []Trial) int

SelectBest returns the index into trials of the structurally-best trial, breaking rank ties by the earliest trial Index (deterministic, and no reason to prefer a later attempt). -1 when trials is empty.

func WarmPricing added in v0.2.0

func WarmPricing()

WarmPricing eagerly loads the live OpenRouter price catalog (once). Call it from a background goroutine at process startup: CostOf's live-catalog fallback otherwise blocks the FIRST cost lookup — which sits on the agent loop's hot path between a model response and its tool dispatch — on a synchronous HTTP fetch (measured 200ms online, up to the 5s client timeout offline).

Types

type Case

type Case struct {
	Name string
	Task string // the goal handed to the agent.
	// TaskSteer, when non-empty, is appended verbatim to Task by RunTrial as a
	// final paragraph. It is an eval-arm knob rather than part of a case's task.
	TaskSteer string
	Fixture   Fixture      // the starting state, materialized fresh per trial.
	Oracle    Oracle       // grades the finished run against ground truth (not self-report).
	Protocol  string       // "tools" (default) or "text".
	Config    agent.Config // knob template (MaxIterations, MaxTokens, RunTimeout, Verify*, …).

	// Sandbox, when non-nil, builds the per-trial sandbox over the freshly
	// materialized fixture dir — the seam for cases whose execution environment is
	// not the host (SWE-bench instances exec inside their official per-instance
	// Docker image, rooted at the extracted /testbed). The trial owns the returned
	// sandbox's lifetime (Close). nil => the default host-local sandbox.
	Sandbox func(ctx context.Context, dir string) (sandbox.Sandbox, error)

	// Setup, when non-nil, runs after the fixture is materialized and the
	// per-trial sandbox exists, but before the agent/runner starts. It can do
	// case-specific in-sandbox measurement and return a per-trial LadderVerify
	// command. The returned command overrides LadderVerify for this trial,
	// including the empty string (used to refuse ladder runs whose signal could
	// not be validated).
	Setup func(ctx context.Context, root string, sb sandbox.Sandbox) (SetupResult, error)

	// Tools, when non-nil, builds the agent's toolset from the per-trial sandbox.
	// It is the seam for replaying a case under the PRODUCTION toolset of the binary
	// it mirrors: commit-msg and issue-bot run ReadOnlyTools (no write/edit), not the
	// full DefaultTools the loop falls back to — so without this a "review" case
	// would be handed mutation tools it never has in production, and could behave
	// (and be graded) unfaithfully. nil => the loop's DefaultTools.
	Tools func(sb sandbox.Sandbox, runTimeout time.Duration) map[string]agent.Tool

	// VCSWorkspace is true when the fixture materializes a real git work tree
	// suitable for VCS-backed runners such as the escalation ladder. Pure
	// in-memory fixtures (MapFixture) must leave this false so the ladder arm
	// can reject them cleanly rather than half-working.
	VCSWorkspace bool

	// LadderVerify is the in-run command the ladder uses as the red/green signal
	// for each rung attempt. When empty, the case verifies only via the held-out
	// oracle and is NOT ladder-compatible — validateLadderCases rejects it. The
	// case constructor sets this when it can provide a meaningful per-trial
	// verify command (e.g. a suite that the agent can pass by completing the
	// task). It is independent of the CLI -verify-cmd flag, which is an optional
	// self-check for the single-model path and is not a substitute for a
	// case-declared ladder signal.
	LadderVerify string
}

Case is one task the harness can run against any model: the prompt, the pristine starting state (Fixture), the independent grader (Oracle), and the agent knobs to run it under (Config — minus the per-trial fields the runner fills: Model, Sandbox, Root, Obs, Task). Protocol picks the loop, mirroring cmd/agent: "tools" (default, native function-calling) or "text". A "tools" case refuses to run (infra failure) on a model that lacks tool support, instead of falling back to the text loop, to avoid measurement confounds.

type Cell

type Cell struct {
	Case   string
	Model  string
	Trials []Trial

	// PricingCoverage_ and UnpricedModels_ are additive report fields
	// populated by WriteFiles before JSON serialisation so downstream
	// consumers of report.json can see per-cell pricing honesty without
	// recomputing from trials. The methods PricingCoverage() and
	// UnpricedModels() compute the same values on the fly for callers that
	// don't go through WriteFiles (tests, markdown).
	PricingCoverage_ string   `json:"pricing_coverage,omitempty"`
	UnpricedModels_  []string `json:"unpriced_models,omitempty"`
}

Cell is the n trials of one (Case × Model) pair — the unit a metric is computed over. The methods are the report's vocabulary: a pass-rate with a confidence interval (not a bare fraction), the false-positive rate (the honesty number), the outcome histogram, and cost/convergence percentiles.

func (Cell) Best

func (c Cell) Best() (Trial, bool)

Best returns the cell's selected trial (see SelectBest). ok is false for an empty cell.

func (Cell) CostP

func (c Cell) CostP(p float64) (cost float64, ok bool)

CostP is the pth percentile of per-trial USD cost across the cell's PRICED trials — the comparable cost-per-run number (a slow, token-hungry passer is expensive even at a cheap per-token rate). ok=false when no trial was priced, so the report renders "—" rather than a misleading $0.

func (Cell) CostTotal

func (c Cell) CostTotal() (total float64, ok bool)

CostTotal sums the USD cost of every priced trial in the cell — what this cell actually cost to measure, reviewer bill included (R4b's sweep-cost line underreported 6.5× by pricing only the solver). ok=false when nothing was priced. The $/trial percentile column stays solver-only on purpose: it compares MODELS, while this compares BILLS.

Ladder trials: t.Cost is already all-in (solver + reviewer + planner summed by ladder.priceAttempt across every attempt), so PlanCost/ReviewCost are NOT added — they hold the WINNING attempt's components and would double-count.

func (Cell) FalsePositiveRate

func (c Cell) FalsePositiveRate() float64

FalsePositiveRate is FalsePositives/N — the metric the closing verification gate (VerifyCmd) is meant to drive toward zero.

func (Cell) FalsePositives

func (c Cell) FalsePositives() int

FalsePositives counts trials where the agent claimed done (Answered) but the oracle failed it — the dangerous failure the bake-offs surfaced.

func (Cell) Infra

func (c Cell) Infra() int

Infra counts trials that failed with an infra error (Err != "").

func (Cell) ItersP

func (c Cell) ItersP(p float64, onlyPass bool) int

ItersP returns the pth percentile (0..100) of iteration counts. onlyPass restricts to passing trials — convergence is only meaningful for runs that actually solved the task (a run that looped to the cap tells you nothing about how fast success arrives).

func (Cell) LadderCostByModel

func (c Cell) LadderCostByModel() map[string]float64

LadderCostByModel aggregates CostByModel across all ladder trials in this cell. Unpriced models are absent from the map. Returns nil when there are no ladder trials or no per-model data.

func (Cell) LadderEscalationRate

func (c Cell) LadderEscalationRate() float64

LadderEscalationRate is escalations / ladder-trials. 0 when no ladder trials.

func (Cell) LadderEscalations

func (c Cell) LadderEscalations() int

LadderEscalations counts ladder trials that escalated past rung 1.

func (Cell) LadderMeanAttempts

func (c Cell) LadderMeanAttempts() float64

LadderMeanAttempts returns the mean number of attempts across ladder trials. 0 when no ladder trials.

func (Cell) LadderN

func (c Cell) LadderN() int

LadderN counts trials in the cell that have ladder metadata (i.e. were run by a ladder arm). 0 for non-ladder cells.

func (Cell) LadderTouchRateByModel

func (c Cell) LadderTouchRateByModel() map[string]float64

LadderTouchRateByModel returns, for each model, the fraction of ladder trials in this cell where that model had nonzero cost. Returns nil when there are no ladder trials.

func (Cell) LatencyP

func (c Cell) LatencyP(p float64) int64

LatencyP is the pth percentile of per-trial agent wall-clock (ms) across the cell — the time axis, told apart from token spend: a model can be cheap per token yet slow per run (high reasoning latency, slow tools). 0 when no step timing was captured (e.g. a pre-timing trace replay).

func (Cell) N

func (c Cell) N() int

N is the trial count.

func (Cell) Outcomes

func (c Cell) Outcomes() map[agent.Outcome]int

Outcomes is the histogram of self-reported terminal states across the cell — answered / hit_cap / killed_* / unverified — so a failing cell shows HOW it failed (looped to cap vs killed by a detector vs a false answer).

func (Cell) PassRate

func (c Cell) PassRate() float64

PassRate is Passes/N (0 when empty).

func (Cell) PassRateCI

func (c Cell) PassRateCI() (lo, hi float64)

PassRateCI is the Wilson 95% interval for the pass-rate — honest about small N. With 7/10 passing it reports ≈[0.40, 0.89], so "0.7" is never mistaken for a precise measurement off ten noisy samples.

func (Cell) PassRateInfraExcluded

func (c Cell) PassRateInfraExcluded() float64

PassRateInfraExcluded is (Pass && Err == "") / (N - Infra). 0 when N == Infra.

func (Cell) Passes

func (c Cell) Passes() int

Passes counts trials the ORACLE passed (the real grade, not the self-report).

func (Cell) PricingCoverage

func (c Cell) PricingCoverage() string

PricingCoverage classifies the cell by how completely its trials are priced: "full" (every trial has all applicable components priced), "partial" (some component priced, some not), "none" (nothing priced). For ladder trials the Ladder.Priced flag is the single all-in gate — ladder.Cost already includes solver + reviewer + planner.

func (Cell) PromptTokensP

func (c Cell) PromptTokensP(p float64) int

PromptTokensP is the pth percentile of prompt tokens across all trials — the cost axis (capability shows up as token spend, not just pass/fail).

func (Cell) ReasoningTokensP

func (c Cell) ReasoningTokensP(p float64) int

ReasoningTokensP is the pth percentile of reasoning tokens across all trials — where a thinking model's completion spend actually goes (a subset of completion tokens, already billed). 0 for non-thinking models, which is the useful signal.

func (Cell) ReviewStatuses

func (c Cell) ReviewStatuses() map[string]int

ReviewStatuses aggregates the status values of every trial's ReviewReport into a count histogram. Returns nil when no trial carries a review report (gate was off for the whole cell). Ladder trials only contribute the top-level trial Review; per-attempt review reports are not carried on AttemptRecord, so the histogram aggregates top-level review statuses.

func (Cell) Split

func (c Cell) Split() bool

Split reports whether the cell mixes passing and failing trials — the only cells where selection has any work to do, and the denominator of the selection-accuracy metric.

func (Cell) UnpricedModels

func (c Cell) UnpricedModels() []string

UnpricedModels returns the deduplicated set of model ids that were seen in this cell but not priced. For non-ladder trials each unpriced component contributes its model id (solver, planner, reviewer). For ladder trials only the top-level Priced flag is available — individual models inside the ladder cannot be distinguished — so the arm label itself is reported when unpriced.

type CommandOracle

type CommandOracle struct {
	Label   string        // short name for the report Detail (e.g. "suite").
	Cmd     string        // the success command; exit 0 == pass.
	Timeout time.Duration // 0 = defaultOracleTimeout.
}

CommandOracle passes iff a shell command exits zero in the post-run fixture. It is the domain-specific verifier HP-5 calls for where one exists — "go test ./...", a build, a property check — run through the sandbox so it sees exactly what the agent left on disk. Label names it in multi-oracle Detail output.

func (CommandOracle) Grade

func (o CommandOracle) Grade(ctx context.Context, in GradeInput) Grade

Grade runs Cmd and passes only on a zero exit. A command that fails to start is a non-pass with the start error as Detail (we could not confirm success).

type Fixture

type Fixture interface {
	Materialize(dir string) error
	Describe() string
}

Fixture materializes a case's pristine starting state into a fresh directory. Materialize is ADDITIVE (it writes files, never deletes), so the same type also serves HeldOut's drop-in step: dropping a held-out test into an already-populated run dir is just another Materialize over the same dir.

type GitFixture

type GitFixture struct {
	RepoRoot string // path to the live git repo to archive FROM (the source of truth).
	Ref      string // commit-ish to materialize (e.g. "ba3ba65^" for "the parent of the fix").
}

GitFixture materializes a REAL repository tree at a pinned commit into the trial dir — the self-history ingest path (HP-11 task diversity). Where MapFixture holds a hand-authored toy in-code, GitFixture archives the actual bytes the repo had at a base commit, so a case can be "the repo as it stood before commit X, minus the fix" — a genuine multi-file task that exercises cross-file navigation and triggers the context-window faces of HP-1 that a <100-LOC toy never can. It is additive (it only writes files), so a HeldOut oracle still layers its dropped tests on top exactly as for MapFixture.

The materialized tree is a hermetic build IFF its module deps resolve from the host cache. The one thing that does NOT survive a copy out of the live repo is a RELATIVE replace directive (e.g. `replace X => ../mneme`): the sibling it points at is not beside the temp dir. Materialize rewrites every relative filesystem replace to an ABSOLUTE path resolved against RepoRoot (where the original go.mod lived, so the relative target meant RepoRoot/../mneme), which keeps the local-dep build working without a per-module allowlist.

func (GitFixture) Describe

func (g GitFixture) Describe() string

Describe names the pinned source state for the manifest/logs.

func (GitFixture) Materialize

func (g GitFixture) Materialize(dir string) error

Materialize streams `git archive <ref>` from RepoRoot into dir, then fixes up relative replace directives so the local-dep build resolves from the temp dir.

type Grade

type Grade struct {
	Pass   bool
	Detail string

	// NoAttempt marks a run that left NOTHING gradable — e.g. an unchanged
	// working tree (SWE-bench's empty model patch). Distinct from a wrong
	// attempt: best-of-N selection (SelectBest) demotes these below every trial
	// that did work, because an `answered` over a no-op is a confident no-op.
	NoAttempt bool
}

Grade is an oracle's verdict: did the run actually satisfy the case, and why. Detail is the evidence — the failing held-out test, the wrong answer — so a failed cell in the report is diagnosable without re-running.

type GradeInput

type GradeInput struct {
	Root    string
	Sandbox sandbox.Sandbox
	Result  *agent.RunResult

	// SetupCreated records untracked paths that Setup created, keyed by path.
	// The entry is the complete post-Setup git tree identity; an oracle may
	// exclude a path only while its current entry remains exactly this value.
	SetupCreated map[string]TreeEntry
}

GradeInput is everything an oracle needs to judge a run. Root is the fixture dir AFTER the run — the real on-disk effect to inspect. Sandbox is the same fence the agent used, so an oracle runs its checks through the identical boundary (and rooted at the same dir). Result carries the agent's typed outcome and trace: read it, never trust it as the verdict.

type HeldOut

type HeldOut struct {
	Label   string        // names this oracle in Detail (e.g. "held-out").
	Drop    Fixture       // the held-out files, written into Root after the run.
	Verify  string        // the command run over the augmented fixture; exit 0 == pass.
	Timeout time.Duration // 0 = defaultOracleTimeout.
}

HeldOut is the suite-green-≠-correct guard (DOGFOOD R9/R10): it Materializes extra files the agent NEVER saw — novel/adversarial cases — into the finished fixture, then runs Verify over the augmented suite. A run that overfit the handed tests (grok's parser that accepted "2+3$") passes the original suite but fails here. Drop is additive (it only adds files), so the original suite still runs alongside the held-out cases.

func (HeldOut) Grade

func (o HeldOut) Grade(ctx context.Context, in GradeInput) Grade

Grade drops the held-out files then defers to a CommandOracle on Verify.

type Manifest

type Manifest struct {
	SchemaVersion  string   `json:"schema_version"` // bump when Trial/Cell/Manifest shape changes, so a diff tool can refuse to compare incompatible reports instead of misreading absent-vs-zero fields.
	GeneratedAt    string   `json:"generated_at"`
	GitSHA         string   `json:"git_sha"`
	GoVersion      string   `json:"go_version"`
	TrialsPer      int      `json:"trials_per_cell"`
	Models         []string `json:"models"`
	Cases          []string `json:"cases"`
	TaskSteerArmed bool     `json:"task_steer_armed,omitempty"`
}

Manifest is the reproducibility record — pinned ids and settings so a report is a comparable artifact, not a one-off. The CLI fills it (it owns time, git, and the env), keeping this package pure and testable.

type MapFixture

type MapFixture map[string]string

MapFixture is the simplest fixture: a path→content map written verbatim into the target dir (parent dirs created as needed). Paths are slash-separated and relative to the dir root. It keeps a small fixture in-code and hermetic — no embed, no nested-module embed restriction (a fixture's own go.mod is just a "go.mod" key), no testdata build interplay. A large fixture is better served by a future embed-backed DirFixture; this is what the first slice ships.

func (MapFixture) Describe

func (m MapFixture) Describe() string

Describe lists the fixture's paths in sorted order (a stable, scannable summary for the manifest / logs).

func (MapFixture) Materialize

func (m MapFixture) Materialize(dir string) error

Materialize writes every entry, creating parent directories first.

type Model

type Model struct {
	Label       string
	Provider    llm.Provider
	Run         TrialRunFunc
	RequiresVCS bool
}

Model pairs a provider with the human-facing id used in the report. The provider's Name() is its registered identity ("openrouter"), not the model string the run actually exercised ("openai/gpt-4o-mini"), so the label is carried explicitly — the report compares models, and a row needs the real id.

When Run is set, RunTrial delegates the solve entirely to this function, skipping the normal provider/protocol selection. RequiresVCS gates the pre-run VCS initialisation needed by runners that depend on a clean git work tree (e.g. the ladder).

type Oracle

type Oracle interface {
	Grade(ctx context.Context, in GradeInput) Grade
}

Oracle grades a finished run against GROUND TRUTH, independent of what the agent claimed. This separation is the harness's whole reason to exist: the bake-offs (DOGFOOD R9/R10) showed models report Outcome=Answered while the build is red, so the agent's self-report and the truth are different signals. An Oracle answers only the second question, the same way a human grader did in the dogfood rounds — but reproducibly and at N-run scale.

func AllOf

func AllOf(oracles ...Oracle) Oracle

AllOf passes iff every oracle passes, evaluated IN ORDER and short-circuiting on the first failure (whose Detail it returns). Order matters: a CommandOracle on the original suite placed before a HeldOut runs while the held-out files are not yet present, so "passed the suite but failed held-out" and "failed the suite" stay distinguishable in the report — the overfit signal vs the never-solved signal.

type Price

type Price struct {
	InPerM        float64 // USD per 1M prompt tokens.
	OutPerM       float64 // USD per 1M completion tokens.
	CacheReadPerM float64 // USD per 1M cache-read prompt tokens.
}

Price is a model's list price in USD per 1,000,000 tokens, split by prompt vs completion (the two bill at different rates). It is the cost axis the DOGFOOD bake-offs kept asking for: capability is only half the picture — "5/5 at what price?" is the other half, and it splits a field that pass-rate alone collapses (gemini-3.1-pro and opus-4.8 both 5/5, but one spends ~4× the tokens).

func (Price) Cost

func (p Price) Cost(u llm.Usage) float64

Cost returns the USD cost of one trial's token usage at this price.

When CacheReadPerM > 0, Usage.CachedTokens (a subset of PromptTokens) are billed at that rate, and only the remainder at InPerM.

When CacheReadPerM == 0, cached tokens are billed at the full prompt rate: Usage.CachedTokens is a subset of PromptTokens that is already counted once, and OpenRouter discounts it — so NOT subtracting it is a deliberate, documented over-estimate (a cost ceiling), never a silent under-count.

type Report

type Report struct {
	Manifest Manifest `json:"manifest"`
	Cells    []Cell   `json:"cells"`
}

Report is the aggregate of a sweep: the manifest plus every cell's trials. It renders to Markdown (human) and JSON (machine-diffable, for regression diffs).

func Run

func Run(ctx context.Context, cases []Case, models []Model, n int) *Report

Run sweeps every (Case × Model) pair n times, sequentially, and aggregates the trials into a Report. n trials per cell is the point (HP-11): a single run measures the dice — the bake-offs flipped the same model pass↔fail across runs — so the unit of measurement is a distribution, not one boolean. For a real multi-model sweep use RunConcurrent; this is the simple, deterministic path.

func RunConcurrent

func RunConcurrent(ctx context.Context, cases []Case, models []Model, n, workers int, onDone func(Trial)) *Report

RunConcurrent is the same sweep with up to `workers` trials in flight at once. Trials are independent — each materializes its OWN pristine temp fixture and sandbox (trial.go) — so concurrency is safe and the wall-clock of a 30-run roster sweep drops from sum-of-runs to ceil(runs/workers) waves. Results are written into preallocated per-cell slots (distinct indices, no append race), so the report's cell/trial order is identical to the sequential Run regardless of completion order. onDone, if non-nil, is called once per finished trial (from a worker goroutine — it must be concurrency-safe) for live progress.

func (*Report) Markdown

func (r *Report) Markdown() string

Markdown renders the human-facing report: a manifest header, then one table per case with a row per model.

func (*Report) SweepCost

func (r *Report) SweepCost() (total float64, ok bool)

SweepCost sums the USD cost of every priced trial across the whole report — the bottom-line "what did this run cost". ok=false when nothing was priced.

func (*Report) WriteFiles

func (r *Report) WriteFiles(dir string) error

WriteFiles archives the report to dir: report.md (human) and report.json (machine-diffable, the input to a future regression diff). The dir is created if absent.

type ReviewFateStats

type ReviewFateStats struct {
	Runs     int // runs whose report named this reviewer.
	Rounds   int // total review rounds spent.
	Repaired int // blocked, fed back, cleared on a later round.
	Refuted  int // repro PASSED — the claim was execution-refuted.
	Expired  int // still blocking when rounds/run ran out.
	Notes    int // never blocked (severity note / under the confidence gate).
	Dropped  int // failed deterministic validation (quote/fence).
}

ReviewFateStats aggregates one reviewer model's finding fates.

func (ReviewFateStats) Blockers

func (s ReviewFateStats) Blockers() int

Blockers is how many findings ever stood as (or were escalated toward) blocking: the denominator of the plan's FP formula.

func (ReviewFateStats) FPRate

func (s ReviewFateStats) FPRate() float64

FPRate is the plan's calibration metric: refuted+expired / total blockers (refuted = provably wrong; expired = never converted into a fix — the pessimistic proxy until human labels exist). NaN-free: 0 when no blockers.

type SetupResult

type SetupResult struct {
	// LadderVerify, when non-nil, overrides Case.LadderVerify for this trial.
	// A pointer distinguishes "no override" from "measured empty; refuse".
	LadderVerify *string
}

type TreeEntry added in v0.2.0

type TreeEntry struct {
	Mode   string
	Type   string
	Object string
}

TreeEntry is a git tree entry's full identity. Object alone is insufficient: mode distinguishes executable files and symlinks from regular files.

type Trial

type Trial struct {
	Case      string
	Model     string
	RunID     string        // the agent run's stable ID (RunResult.ID) — the P1 spine, so a Trial can be correlated with its persisted transcript.
	Index     int           // 1-based trial number within the cell.
	Outcome   agent.Outcome // the agent's self-reported terminal state.
	Answer    string
	Iters     int
	Usage     llm.Usage
	Pass      bool   // the ORACLE's verdict — the actual grade.
	Detail    string // the oracle's evidence.
	NoAttempt bool   // the oracle found nothing gradable (Grade.NoAttempt) — demoted by best-of selection.
	Err       string // infra failure (provider/transport), distinct from a non-pass.

	Cost   float64 // USD cost of this trial's Usage at the pinned Pricing (meaningless unless Priced).
	Priced bool    // whether the model had a Pricing entry — false => render "—", not $0.

	// Plan is the opening plan-stage report (nil when Config.Planner was unset).
	// Persisted per trial so planner activity and cost are recorded facts.
	Plan       *agent.PlanReport `json:"plan,omitempty"`
	PlanCost   float64           // USD of Plan.Usage at the PLANNER model's pinned Pricing.
	PlanPriced bool              // false when the planner model has no Pricing entry (or Plan is nil).

	// Review is the closing review-gate report (nil when Config.Reviewer was
	// unset). Persisted per trial so review activity is a recorded fact, not
	// something to infer from outcome flips and billing deltas (the R4b gap).
	Review       *agent.ReviewReport `json:"review,omitempty"`
	ReviewCost   float64             // USD of Review.Usage at the REVIEWER model's pinned Pricing.
	ReviewPriced bool                // false when the reviewer model has no Pricing entry (or Review is nil).

	// LatencyMs is the agent's wall-clock for this trial (summed model + tool time
	// across steps). Derived from Steps so it survives in report.json after the full
	// trace is dropped — lets the report separate slow runs from token-heavy ones.
	LatencyMs int64

	// Protocol is the loop actually used: "tools", "text", or "refused" (infra
	// failure when a tools case met a no-tools model).
	Protocol string `json:"protocol"`

	// Ladder holds per-trial escalation-ladder metadata when the trial was
	// executed by a ladder arm. nil for normal single-model trials — the
	// omitempty tag keeps existing report JSON stable.
	Ladder *TrialLadder `json:"ladder,omitempty"`

	// Steps is the full think->act->observe trace, kept for per-trial archival but
	// EXCLUDED from report.json (json:"-") so the aggregate report stays compact —
	// WriteFiles writes each trace to its own file under traces/ instead.
	Steps []agent.Step `json:"-"`
}

Trial is one execution of a Case by a Model: the agent's typed self-report (Outcome/Answer/Iters/Usage) PLUS the oracle's independent verdict (Pass/ Detail). Keeping both side by side is what makes the false-positive metric computable — a run where Outcome==Answered but Pass==false is a model that claimed success on a task it did not finish (the #1 bake-off failure). It is a flat value (not the full *RunResult) so the report JSON stays compact; the full Step trace is intentionally out of this slice (a follow-up can archive per-trial traces alongside the report).

func RunTrial

func RunTrial(ctx context.Context, c Case, m Model, index int) Trial

RunTrial executes one trial: materialize a PRISTINE fixture into a fresh temp dir (never the live repo, never a reused dir), run the agent against it, then grade the resulting on-disk state with the case's oracle. The temp dir is removed afterward — the fixture is reproducible, and the verdict is what we keep. err-shaped infra failures from the loop are recorded on the Trial (as .Err) rather than returned, so one flaky provider call can't abort a whole suite sweep; the trial just counts as a non-pass.

When m.Run is set (a custom trial runner, e.g. the escalation ladder), RunTrial delegates the solve to that function instead of the normal single-model provider path. m.RequiresVCS gates a pre-run git-baseline initialisation for runners that need a clean work tree.

func (Trial) FalsePositive

func (t Trial) FalsePositive() bool

FalsePositive reports the run that claimed done but did not actually finish: the agent terminated with Answered, yet the oracle failed it. This is the honesty signal the closing verification gate (VerifyCmd) exists to drive to zero — measuring it here closes the loop on that feature.

type TrialLadder

type TrialLadder struct {
	Attempts   int     // total attempts across all rungs.
	WinnerRung int     // 1-based rung index of the winning attempt; 0 when nothing won.
	Escalated  bool    // true when any attempt went past rung 1 (the NORTH-STAR guard).
	Cost       float64 // USD cost of all ladder attempts summed; valid only when Priced is true.
	Priced     bool    // whether at least one attempt had a cost known to the price table.

	// CostByModel is the per-model dollar attribution: solver cost under its rung
	// model id, reviewer cost under the reviewer model id, planner under the
	// planner model id. The sum across entries equals Cost (modulo pricing
	// gaps). nil for non-ladder trials (omitempty keeps JSON compact).
	CostByModel map[string]float64 `json:"cost_by_model,omitempty"`
}

TrialLadder records the per-trial escalation-ladder metadata for a trial run by a ladder arm. Zero values mean "no info available" — the report renders these as "—".

type TrialRunFunc

type TrialRunFunc func(context.Context, agent.Config, string) (*agent.RunResult, *TrialLadder, error)

TrialRunFunc is an optional custom trial runner. When set on a Model, RunTrial delegates the solve to this function instead of the normal single-model provider path. The runner receives the per-trial agent.Config (already populated with sandbox, root, task, tools) and the case name, and must return a RunResult plus, optionally, ladder metadata. nil ladder => no ladder info.

Directories

Path Synopsis
suite

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL