Skip to content

eval ​

Helpers for judging eval runs from Agency code. (Running suites from Agency was removed 2026-07-30: the old binding predated workdir seeding and diverged from agency eval run semantics. Run suites with the CLI.

SAFETY CONSTRAINT for whoever adds run-from-Agency back: the old binding routed every task through std::agency.run SPECIFICALLY so subprocess interrupts hit the CALLING AGENT's handler stack. A thin binding over runSuite would lose that: runSuite's default runner blanket-approves every child interrupt from TypeScript — correct for a headless CLI invocation, wrong for a suite launched by an agent whose handlers were meant to gate those interrupts. A future binding must route execution through std::agency.run, or inject a runner that defers to the caller's handlers — never runSuite's default.)

Extract and judge eval records ​

ts
import { evalExtract, evalJudge } from "std::agency/eval"

node main() {
  const record = evalExtract("runs/demo/inputs/capital-france/statelog.jsonl")
  print(record.evalOutputs)

  const verdict = evalJudge(
    "Prefer the answer that names the capital exactly.",
    "runs/a/inputs/capital-france/eval-record.json",
    "runs/b/inputs/capital-france/eval-record.json",
  )
  print(verdict.winner)
}

Judge whole run directories ​

ts
import { evalJudgeSuite } from "std::agency/eval"

node main() {
  const verdict = evalJudgeSuite("runs/baseline", "runs/candidate")
  print(verdict.winner)
}

Types ​

EvalValue ​

ts
export type EvalValue = {
  value: any;
  threadId?: string;
  tMs: number;
  truncated?: boolean
}

(source)

EvalRecord ​

ts
export type EvalRecord = {
  traceId: string;
  recordVersion: number;
  formatVersion: number;
  durationMs: number;
  source: string;
  evalValues: EvalValue[];
  evalOutputs: EvalValue[];
  threads: Record<string, any>[];
  events: Record<string, any>[];
  interrupts: Record<string, any>[];
  errors: Record<string, any>[];
  incomplete: Record<string, any>[];
  metrics: Record<string, any>;
  warnings: string[]
}

(source)

PairwiseVerdictInput ​

ts
export type PairwiseVerdictInput = {
  path: string;
  response: string;
  truncated?: boolean
}

(source)

PairwiseVerdict ​

ts
export type PairwiseVerdict = {
  verdictVersion: number;
  goal: string;
  inputs: PairwiseVerdictInput[];
  winner: string;
  confidence: number;
  reasoning: string;
  generatedAt: string
}

(source)

JudgeAggregationPolicy ​

ts
export type JudgeAggregationPolicy = {
  samples: number;
  confidenceThreshold: number;
  marginThreshold: number;
  positionBias: string
}

(source)

VerdictSide ​

ts
export type VerdictSide = {
  path?: string;
  status: string;
  response?: string;
  truncated?: boolean;
  errorMessage?: string
}

(source)

JudgeSample ​

ts
export type JudgeSample = {
  winner: string;
  confidence: number;
  reasoning: string;
  order: string
}

(source)

InputVerdict ​

ts
export type InputVerdict = {
  inputId: string;
  goal: string;
  inputs: VerdictSide[];
  winner: string;
  confidence: number;
  reasoning: string;
  samples: JudgeSample[];
  generatedAt: string
}

(source)

SuiteVerdict ​

ts
export type SuiteVerdict = {
  verdictVersion: number;
  generatedAt: string;
  policy: JudgeAggregationPolicy;
  winsA: number;
  winsB: number;
  ties: number;
  winner: string;
  perInput: InputVerdict[]
}

(source)

Functions ​

evalExtract ​

ts
evalExtract(statelogPath: string): EvalRecord

Extract a structured eval record from a statelog file. Returns the same record agency eval extract writes to disk, but directly, so eval pipelines composed in Agency can inspect or judge it without going through a temporary file.

@param statelogPath - Path to a .statelog.jsonl file produced by an agent run (e.g. the file under runs/<run-id>/inputs/<input-id>/ after an eval run)

The shape mirrors the on-disk eval-record format. Top-level fields (traceId, durationMs, evalValues, evalOutputs, warnings) are the most commonly consumed. The nested arrays (threads, events, interrupts, errors, incomplete) are loosely typed because their schemas are large and evolve independently. Consumers can JSON-inspect as needed.

Parameters:

NameTypeDefault
statelogPathstring

Returns: EvalRecord

(source)

evalJudge ​

ts
evalJudge(
  goal: string,
  recordPathA: string,
  recordPathB: string,
): PairwiseVerdict

Pairwise-judge two eval records against a goal. Returns a structured verdict naming the winner ("A", "B", or "tie"), the judge's confidence as an integer from 0 to 100, and the reasoning the judge produced. Both record paths must point at JSON files in the EvalRecord shape produced by extracting an eval record.

@param goal - What the judge should grade against (typically a per-input goal from an eval suite) @param recordPathA - Path to the first eval record JSON file @param recordPathB - Path to the second eval record JSON file

Runs the bundled pairwise-judge program in a subprocess, so a real LLM call happens per invocation. Budget accordingly when looping. Argument order can matter: judge LLMs slightly prefer one position over the other, so high-precision callers should invoke twice with swapped order and reconcile the verdicts.

Parameters:

NameTypeDefault
goalstring
recordPathAstring
recordPathBstring

Returns: PairwiseVerdict

(source)

evalJudgeSuite ​

ts
evalJudgeSuite(
  runA: string,
  runB: string,
  samples: number = 3,
  confidenceThreshold: number = 50,
  marginThreshold: number = 0,
  positionBias: "swap" | "none" = "swap",
): SuiteVerdict

Judge two eval run directories by input id and aggregate the results into a suite verdict. Input ids and goals come from each run directory itself (the input.json files the run wrote). Missing or failed input records are handled deterministically without calling the LLM judge; successful inputs are judged pairwise.

@param runA - Path to the first eval run directory @param runB - Path to the second eval run directory @param samples - Judge samples per input @param confidenceThreshold - Minimum input confidence counted as a suite win @param marginThreshold - Suite win margin required to avoid an overall tie @param positionBias - Whether to swap A/B order across samples to cancel judge position bias

Parameters:

NameTypeDefault
runAstring
runBstring
samplesnumber3
confidenceThresholdnumber50
marginThresholdnumber0
positionBias"swap" | "none""swap"

Returns: SuiteVerdict

(source)