Documentation
¶
Overview ¶
Package eval is Halos's offline replay harness: it runs a suite of coding tasks against harness variants (CLI version x model x settings) in isolated environments, scores each run with the task's own success check, and renders a scorecard with bootstrap CIs and a paired comparison.
Task format (evals/tasks/<name>/task.yaml) and rough Harbor mapping:
id -> Harbor task name repo -> environment/ (Dockerfile COPY/clone of the project under test) setup -> environment build/setup steps prompt -> instruction.md check -> tests/test.sh (exit 0 = pass; we do not read a reward file) graders -> extra pluggable graders (command, file, diff, judge); see grade.go timeout -> [agent] timeout budget_usd -> no Harbor equivalent; passed to the CLI's spend cap tags -> metadata tags
Index ¶
- Constants
- Variables
- func AppendHistory(path string, entries ...HistoryEntry) error
- func CheckSecureURL(raw string) error
- func Drivers() map[string]Driver
- func Fence(parts []Part) (prompt, notice string, err error)
- func GradedPairs(path string) (map[string]bool, error)
- func HistoryIDs(path string) (graded, agent map[string]bool, err error)
- func HistoryScores(path string) (map[string]PairScore, error)
- func OnlineTable(rs []OnlineResult) string
- func SameCalls(a, b Action) bool
- func SameTools(a, b Action) bool
- func SessionUnit(session string) string
- func WriteStepCases(dir string, cases []StepCase) error
- type Action
- type AgentArm
- type AgentResult
- type ArmStats
- type Call
- type CaseSummary
- type ClaudeDriver
- type CodexDriver
- type Command
- type Comparison
- type Criterion
- type DepthBucket
- type DockerRunner
- type Driver
- type Env
- type ExecResult
- type FilePairs
- type Gate
- type GeminiDriver
- type Grade
- type GraderSpec
- type HTTPLLM
- type HarvestOptions
- type HarvestResult
- type HistoryEntry
- type Job
- type Judge
- type JudgeCache
- type JudgeConfig
- type JudgeInfo
- type LLM
- type LLMRequest
- type LocalRunner
- type Matrix
- type MatrixHarness
- type MatrixProvider
- type MetricDelta
- type OTLPExporter
- type OnlineOptions
- type OnlineResult
- type OpsResult
- type PairScore
- type PairSource
- type Part
- type Provenance
- type Rate
- type Redactor
- type Rubric
- type RunOptions
- type Runner
- type Scorecard
- type StepCase
- type StepModel
- type StepResult
- type StepRunOptions
- type StepSample
- type StepScorecard
- type Suite
- type Task
- type TaskDiff
- type Thresholds
- type TokenUsage
- type Trial
- type Usage
- type Variant
- type VariantScore
- type Verdict
Constants ¶
const ( GateShip = "ship" // candidate is non-inferior on every gated metric GateHold = "hold" // inconclusive: rerun with more trials or tasks GateBlock = "block" // candidate is confidently worse )
Regression verdicts of the gate, worst last.
const ( MetricPass = "pass_rate" MetricCost = "cost_usd" MetricTokens = "tokens" MetricWall = "wall_ms" MetricToolCalls = "tool_calls" )
Metric keys of Comparison.Deltas.
const ( GraderCommand = "command" // run exits 0 GraderFile = "file" // assertions on one file's existence/content GraderDiff = "diff" // assertions on which files the agent changed GraderJudge = "judge" // LLM-as-judge rubric over the prompt and the change )
Grader types.
const ( SelectDiverged = "diverged" // candidate's tools differ from the control replay or from production SelectInvalidCall = "invalid-call" // candidate called a tool not offered, or missed a required argument SelectPrematureStop = "premature-stop" // production kept working; the candidate ended its turn SelectJudgeLoss = "judge-loss" // judge scored the candidate below the control by more than JudgeLoss SelectSample = "sample" // stratified random sample by depth )
Selection reasons: why a step became a case.
const ( MatchTools = "tools" // same multiset of tool names (or both stopped) MatchCalls = "calls" // same tools with identical canonical arguments )
Matcher modes for StepCase.Match.
const ( FromProduction = "production" // recovered from the session's next mirrored request FromControl = "control" // the control replay, when production's action was not recovered )
Expected-action sources.
const ( WireAnthropic = "anthropic-messages" WireOpenAI = "openai-responses" )
Judge wire protocols (names match policy Gateway.Protocols).
const ( MetricJudgeScore = "halo.eval.judge.score" // gauge per pair and arm (halo.unit = pair id), 0..1 MetricJudgeDelta = "halo.eval.judge.delta" // gauge, candidate - control MetricJudgeWinRate = "halo.eval.judge.win_rate" // gauge MetricPairsGraded = "halo.eval.pairs.graded" // sum (delta) MetricJudgeErrors = "halo.eval.judge.errors" // sum (delta) // Operational readings per arm over every pair (ADR-0004: refusal, latency, cost). MetricShadowErrorRate = "halo.eval.shadow.error_rate" // gauge, 0..1 MetricShadowLatency = "halo.eval.shadow.latency_ms" // gauge, mean MetricShadowOutputTokens = "halo.eval.shadow.output_tokens" // gauge, mean // Step-level agent readings (ADR-0014). Fidelity and invalid-call rate are // analysis samples per session and arm (halo.unit = hashed session id); // agreement is a per-experiment gauge. MetricAgentFidelity = "halo.eval.agent.fidelity" // gauge per session and arm, 0..1 MetricAgentInvalidCall = "halo.eval.agent.invalid_call_rate" // gauge per session and arm, 0..1 MetricAgentToolAgreement = "halo.eval.agent.tool_agreement" // gauge, candidate vs control replay MetricAgentPrematureStop = "halo.eval.agent.premature_stop" // gauge per arm, 0..1 )
Online eval metric names (halo.* schema, see internal/telemetry).
const ( VerdictBetter = "better" VerdictWorse = "worse" VerdictNoDiff = "no_significant_difference" )
Verdicts for a paired comparison against control.
const DefaultAgentQuiet = time.Hour
DefaultAgentQuiet is OnlineOptions.AgentQuiet's default.
const DefaultTimeout = 10 * time.Minute
DefaultTimeout applies when a task sets none.
const RedactionRulesVersion = "1"
RedactionRulesVersion names the built-in rule set recorded in every case's provenance. Bump it whenever a built-in rule changes.
Variables ¶
var DefaultKeepFields = []string{
"model", "system", "messages", "tools", "tool_choice", "max_tokens", "temperature", "top_p", "top_k", "stop_sequences", "thinking", "anthropic_version",
"instructions", "input", "max_output_tokens", "parallel_tool_calls", "reasoning", "text", "include",
"contents", "systemInstruction", "generationConfig", "toolConfig",
"inferenceConfig",
}
DefaultKeepFields are the top-level request fields a harvested case keeps: what the model needs to answer the step on each wire protocol. Everything else is dropped, because the rest (Claude Code's metadata.user_id, Codex's prompt_cache_key, user, safety_identifier, previous_response_id...) carries account and session identifiers, not the conversation.
var DefaultSandboxProbes = map[string][]string{"codex": {"codex", "sandbox", "linux", "--full-auto", "true"}}
DefaultSandboxProbes: codex runs `true` under the same workspace-write (Landlock + seccomp) sandbox `codex exec --sandbox workspace-write` uses. Never danger-full-access (AGENTS.md invariant 1): a host that cannot sandbox codex is reported, not worked around. Verified against codex-cli 0.99.0 without Landlock (Docker Desktop): exit 101, "Sandbox(LandlockRestrict)" (testdata/codex/sandbox-probe-no-landlock.txt). The exit-0 case on a Landlock host is not yet recorded.
var DefaultSelect = []string{SelectDiverged, SelectInvalidCall, SelectPrematureStop}
DefaultSelect is the selection when none is given: the step-level failures.
var ErrJudgeSchema = errors.New("judge reply failed schema validation")
ErrJudgeSchema marks a judge reply that failed strict parsing: a grader error, never a pass.
ErrSandboxUnavailable marks a cell the host cannot measure: the harness's own sandbox does not work here (codex workspace-write needs Landlock, which e.g. Docker Desktop's linuxkit kernel lacks). Such trials are never run and never scored as failures; the gate holds instead (see Trial.Unavailable).
Functions ¶
func AppendHistory ¶
func AppendHistory(path string, entries ...HistoryEntry) error
AppendHistory appends entries to the JSONL file at path (created 0644).
func CheckSecureURL ¶
CheckSecureURL requires https, allowing plain http only to a loopback host (local gateways, tests): credentials must never cross a network in clear.
func Fence ¶ added in v0.4.0
Fence lays parts out as the judge does, untrusted bodies inside a fresh per-call nonce delimiter, and returns the prompt plus the sentence (for the system prompt) saying fenced content is data, never instructions.
func GradedPairs ¶
GradedPairs returns the pair ids already recorded in the history file (missing file = none).
func HistoryIDs ¶ added in v0.3.0
HistoryIDs returns the pair ids already judged and the step ids whose agent samples were already exported (missing file = none).
func HistoryScores ¶ added in v0.3.0
HistoryScores returns the judge scores recorded per pair id in the history file (missing file = none; a later reading of a pair wins).
func OnlineTable ¶
func OnlineTable(rs []OnlineResult) string
OnlineTable renders online results as a compact terminal table.
func SameTools ¶ added in v0.3.0
SameTools: both stopped, or both called the same multiset of tools. A malformed step matches only another malformed step.
func SessionUnit ¶ added in v0.3.0
SessionUnit is the analysis unit for a session: a hash, so raw harness session ids never leave the eval host.
func WriteStepCases ¶ added in v0.3.0
WriteStepCases writes one <id>.json per case into dir (created 0700), each 0600. Every file is encoded and re-checked for surviving credentials before the first one is written, so a failure leaves no partial suite.
Types ¶
type Action ¶ added in v0.3.0
type Action struct {
Calls []Call `json:"calls,omitempty"`
// Malformed: the model tried to call a tool and produced something the
// API rejected (Gemini MALFORMED_FUNCTION_CALL, Converse malformed_tool_use).
// An invalid call, not a stop.
Malformed bool `json:"malformed,omitempty"`
// Truncated: the output token cap cut the step; it says nothing about the
// model's decision, so the step is excluded.
Truncated bool `json:"truncated,omitempty"`
}
Action is one model step: the tool calls it made, in order. No calls means the model ended its turn (answered or gave up).
type AgentArm ¶ added in v0.3.0
type AgentArm struct {
// ToolCallRate: share of steps that called at least one tool.
ToolCallRate float64 `json:"toolCallRate"`
// InvalidToolRate: steps calling a tool the request did not offer
// (hallucinated tool names).
InvalidToolRate float64 `json:"invalidToolRate"`
// SchemaErrorRate: steps whose arguments miss a required field or are
// not a JSON object.
SchemaErrorRate float64 `json:"schemaErrorRate"`
// InvalidCallRate: steps with either problem (what the exported
// halo.eval.agent.invalid_call_rate samples per session).
InvalidCallRate float64 `json:"invalidCallRate"`
// PrematureStopRate: among referenced steps where production kept
// working, the share where this arm ended its turn instead.
PrematureStopRate float64 `json:"prematureStopRate"`
// Fidelity: agreement (same tools) with the action production actually
// took. The control's fidelity is the re-sampling noise floor.
Fidelity Rate `json:"fidelity"`
}
AgentArm is one arm's per-step behaviour.
type AgentResult ¶ added in v0.3.0
type AgentResult struct {
Steps int `json:"steps"` // pairs whose both actions parsed
Sessions int `json:"sessions"` // distinct sessions among them
// MultiTurn: steps past the first turn (turns: all).
MultiTurn int `json:"multiTurn"`
// Excluded, not steps: Auxiliary requests that offer no tools (compaction
// summaries, next-speaker checks: both arms just answer), Truncated steps
// (an arm hit its output cap), Retries (byte-identical resends of a step).
Auxiliary int `json:"auxiliary"`
Truncated int `json:"truncated"`
Retries int `json:"retries"`
// ToolAgreement / ArgAgreement: candidate vs control replay, same prefix.
ToolAgreement Rate `json:"toolAgreement"`
ArgAgreement Rate `json:"argAgreement"`
Control AgentArm `json:"control"`
Candidate AgentArm `json:"candidate"`
// Referenced: steps whose production action was recovered.
Referenced int `json:"referenced"`
// FidelityDelta: candidate - control fidelity, paired by step,
// session-clustered CI. Below zero beyond the noise floor = regression.
FidelityDelta MetricDelta `json:"fidelityDelta"`
// Diverged: share of sessions where the candidate's action differed from
// the control replay at some step; FirstDivergence is the mean step at
// which that first happened (among diverged sessions).
Diverged float64 `json:"diverged"`
FirstDivergence float64 `json:"firstDivergence"`
ByDepth []DepthBucket `json:"byDepth"`
// PerStep are the analysis samples the exporter may send, one per step.
PerStep []StepSample `json:"-"`
}
AgentResult is the step-level reading of one shadow experiment. Rates are step-weighted (every step counts once); the exported analysis samples are per session (see StepSample), a different estimand that weights sessions equally.
func AnalyzeAgent ¶ added in v0.3.0
func AnalyzeAgent(pairs []shadow.Pair, seed uint64) *AgentResult
AnalyzeAgent computes the step-level reading over pairs whose both sides answered. It returns nil when no pair's actions could be parsed.
type ArmStats ¶
type ArmStats struct {
ErrorRate float64 `json:"errorRate"`
LatencyMS float64 `json:"latencyMs"`
OutputTokens float64 `json:"outputTokens"`
}
ArmStats summarises one arm over all pairs: ErrorRate is the share of sides that errored or answered non-200 (refusals and overloads included); LatencyMS and OutputTokens are means.
type CaseSummary ¶ added in v0.3.0
type CaseSummary struct {
ID string `json:"id"`
Reasons []string `json:"reasons"`
ExpectedFrom string `json:"expectedFrom"`
Expected []string `json:"expectedTools"`
Provenance Provenance `json:"provenance"`
}
CaseSummary is a case without its content (safe to print or return over MCP).
type ClaudeDriver ¶
type ClaudeDriver struct{}
ClaudeDriver runs `claude -p` with stream-json output.
func (ClaudeDriver) Command ¶
func (ClaudeDriver) Command(t *Task, v Variant) Command
Command: verified against claude 2.1.280 in test/uat (`halo eval run` with the DockerRunner): --restricted keeps Write/Edit, stream-json needs --verbose.
func (ClaudeDriver) Name ¶
func (ClaudeDriver) Name() string
func (ClaudeDriver) Parse ¶
func (ClaudeDriver) Parse(out []byte) (Usage, error)
Parse reads the final `result` message of the stream.
func (ClaudeDriver) SettingsPath ¶
func (ClaudeDriver) SettingsPath() string
SettingsPath is Claude Code's Linux managed-settings location.
type CodexDriver ¶
type CodexDriver struct{}
CodexDriver runs `codex exec --json` with the prompt on stdin.
func (CodexDriver) Command ¶
func (CodexDriver) Command(t *Task, v Variant) Command
Command: --skip-git-repo-check is required because the trial's /work is not a trusted git repo (codex 0.58.0 exits 1 "Not inside a trusted directory" without it; seen in test/uat).
func (CodexDriver) Name ¶
func (CodexDriver) Name() string
func (CodexDriver) Parse ¶
func (CodexDriver) Parse(out []byte) (Usage, error)
Parse reads JSONL events. Codex reports no cost, so CostUSD stays 0. Event shapes verified against codex-rs/exec/src/exec_events.rs (FACTS.md).
Tool errors: a command_execution item that completed with a non-zero exit_code or status failed/declined (codex 0.99 emits both, e.g. exit_code 1 + status "failed"; test/uat/testdata), a failed MCP/other tool item, or a tool item that started but never completed (cut off by a timeout).
Not countable here: a call whose sandbox cannot start at all. On a kernel without Landlock, codex 0.99 emits NOTHING for the exec_command (no item.started, no error item; it exits 0) and only the model sees "Sandbox(LandlockRestrict)". DockerRunner's sandbox preflight catches that case instead and marks the cell unavailable.
func (CodexDriver) SettingsPath ¶
func (CodexDriver) SettingsPath() string
SettingsPath is the verified Unix managed_config.toml location (internal/harness/FACTS.md).
type Comparison ¶
type Comparison struct {
Variant string `json:"variant"`
Control string `json:"control"`
Tasks int `json:"tasks"`
Delta float64 `json:"delta"` // mean per-task pass rate: variant - control
CILo float64 `json:"ciLo"`
CIHi float64 `json:"ciHi"`
Verdict string `json:"verdict"`
// Deltas are paired bootstrap CIs per metric over non-flaky tasks
// (MetricPass, MetricCost, ...); Gate is the ship/hold/block verdict.
Deltas map[string]MetricDelta `json:"deltas,omitempty"`
Gate *Gate `json:"gate,omitempty"`
}
Comparison is a paired (by task) pass-rate comparison vs the control.
type Criterion ¶
type Criterion struct {
Name string `yaml:"name" json:"name"`
Description string `yaml:"description" json:"description"`
Weight float64 `yaml:"weight,omitempty" json:"weight,omitempty"` // default 1
}
Criterion is one scored dimension of a rubric.
type DepthBucket ¶ added in v0.3.0
type DepthBucket struct {
Steps string `json:"steps"` // e.g. "0", "1-4"
N int `json:"n"`
ToolAgreement float64 `json:"toolAgreement"`
}
DepthBucket is tool agreement by conversation depth.
type DockerRunner ¶
type DockerRunner struct {
Bin string // default "docker"
// Image returns the image for a variant; default
// ghcr.io/dshakes/eval-<harness>:<version>.
Image func(Variant) string
// PassEnv names host env vars forwarded into the agent step only.
PassEnv []string
// Network is passed as --network; default "none".
Network string
// Memory (default "4g"), CPUs (default "2") and User (default
// "1000:1000", non-root) bound each container.
Memory, CPUs, User string
// SandboxProbes maps harness -> a command that must exit 0 inside a
// container hardened like the trials before any trial of that harness
// runs (once per runner and image). nil = DefaultSandboxProbes; an empty
// map disables probing.
SandboxProbes map[string][]string
// contains filtered or unexported fields
}
DockerRunner isolates each trial in hardened containers by shelling out to the docker CLI. Each phase gets a FRESH container from the image, with only /work carried over (docker cp out, then in):
setup -> container A (task-author shell; no secrets) agent -> container B (PassEnv secrets via exec -e; nothing A started survives) check -> container C (no secrets; cannot read B's /proc/<pid>/environ)
Every container is removed (`docker rm -f`, killing all its processes) before the next phase starts, on Close, and when a step's context ends.
Remote repos are cloned on the HOST (protocol-allowlisted) and copied in, so the containers need no egress for setup/check (Network "none").
Secrets: PassEnv should hold short-lived gateway tokens (minted per run and scoped to the Halos gateway), never long-lived provider keys: the agent step runs model-driven tool calls that can read its own environment. Recommended networking: set Network to a docker network whose only egress is the Halos gateway; it applies to every phase's container.
Files are streamed in as a tar whose entries are owned by User: plain `docker cp` keeps the host uid (e.g. 501), leaving the non-root agent unable to edit files in place. Verified end to end against a real daemon with the evals/images/fake stub image.
type Driver ¶
type Driver interface {
Name() string
Command(t *Task, v Variant) Command
// SettingsPath is where the rendered managed settings must appear inside
// the environment for the CLI to pick them up.
SettingsPath() string
Parse(stdout []byte) (Usage, error)
}
Driver knows how to run one harness headlessly and read its output. Unknown output fields are always tolerated.
type ExecResult ¶
ExecResult is the outcome of one command. A non-zero ExitCode is not an error; err from Exec means the command could not run or was cancelled.
type FilePairs ¶
type FilePairs struct {
Path string
Keys [][]byte
// AllowPlaintext reads plaintext rows even with Keys set (a store sealed
// part way through its life). Off by default: with keys configured an
// unsealed row is refused (shadow.ErrPlaintextRow).
AllowPlaintext bool
}
FilePairs reads halo-shadow's JSONL pair store, decrypting sealed rows with Keys (current and retired). An unreadable row fails the read: grading a silently partial sample would bias it.
type Gate ¶
type Gate struct {
Verdict string `json:"verdict"` // ship | hold | block
Reasons []string `json:"reasons"`
Flaky []TaskDiff `json:"flaky,omitempty"`
Regressed []TaskDiff `json:"regressed,omitempty"`
Tasks []TaskDiff `json:"tasks,omitempty"`
}
Gate is the regression verdict of one comparison (or, on the scorecard, the worst over all comparisons).
type GeminiDriver ¶
type GeminiDriver struct{}
GeminiDriver runs `gemini -p ... --output-format json`. Flags, settings path and the JSON stats shape are verified against the docs and source (internal/harness/FACTS.md); not run against a real CLI.
func (GeminiDriver) Name ¶
func (GeminiDriver) Name() string
func (GeminiDriver) SettingsPath ¶
func (GeminiDriver) SettingsPath() string
type Grade ¶
type Grade struct {
Grader string `json:"grader"` // type, plus path/rubric when useful
Pass bool `json:"pass"`
Score *float64 `json:"score,omitempty"` // judge only
Rubric string `json:"rubric,omitempty"`
Detail string `json:"detail,omitempty"`
// Error is a grader failure (e.g. judge schema violation): never a pass.
Error string `json:"error,omitempty"`
}
Grade is one grader's outcome on one trial.
type GraderSpec ¶
type GraderSpec struct {
Type string `yaml:"type" json:"type"`
// command
Run string `yaml:"run,omitempty" json:"run,omitempty"`
// file: Path relative to the work dir; Exists defaults to true.
Path string `yaml:"path,omitempty" json:"path,omitempty"`
Exists *bool `yaml:"exists,omitempty" json:"exists,omitempty"`
Contains []string `yaml:"contains,omitempty" json:"contains,omitempty"`
NotContains []string `yaml:"not_contains,omitempty" json:"notContains,omitempty"`
Matches string `yaml:"matches,omitempty" json:"matches,omitempty"` // RE2
// diff: globs (path.Match, slash-separated) over changed paths.
MaxFilesChanged int `yaml:"max_files_changed,omitempty" json:"maxFilesChanged,omitempty"`
MustChange []string `yaml:"must_change,omitempty" json:"mustChange,omitempty"`
MustNotChange []string `yaml:"must_not_change,omitempty" json:"mustNotChange,omitempty"`
// judge: rubric file relative to the declaring task or suite dir (e.g.
// ../rubrics/x.yaml), never outside the evals root.
Rubric string `yaml:"rubric,omitempty" json:"rubric,omitempty"`
// contains filtered or unexported fields
}
GraderSpec is one entry of a task's `graders:`. Fields apply per type.
type HTTPLLM ¶
HTTPLLM speaks the Anthropic Messages or OpenAI Responses wire to the gateway. Temperature is 0: the judge should be as repeatable as the provider allows. UNVERIFIED against a live gateway; tests use httptest.
func NewHTTPLLM ¶ added in v0.4.0
func NewHTTPLLM(c *JudgeConfig) (*HTTPLLM, error)
NewHTTPLLM validates c (https or loopback http, a known wire, a pinned model) and builds its client, reading the credential from c.APIKeyEnv now. The copilot (ADR-0035) shares it with the judge.
func (*HTTPLLM) CompleteUsage ¶ added in v0.4.0
func (h *HTTPLLM) CompleteUsage(ctx context.Context, r LLMRequest) (string, TokenUsage, error)
CompleteUsage is Complete plus the provider's reported token usage (both wires name it usage.input_tokens and usage.output_tokens).
type HarvestOptions ¶ added in v0.3.0
type HarvestOptions struct {
// Experiment limits the harvest to one experiment ("" = all).
Experiment string
// Select lists the reasons that make a step a case (default DefaultSelect).
Select []string
// Scores are judge scores per pair id (HistoryScores); needed by judge-loss.
Scores map[string]PairScore
// JudgeLoss: control minus candidate judge score above which a step is
// selected (default 0.2).
JudgeLoss float64
// PerDepth caps the stratified sample per depth bucket (default 5).
PerDepth int
Seed uint64
// Redactor is required: nothing is harvested unredacted.
Redactor *Redactor
Now func() time.Time
}
HarvestOptions selects and redacts steps.
type HarvestResult ¶ added in v0.3.0
type HarvestResult struct {
Steps int `json:"steps"` // parsed steps considered
// Skipped: selected steps whose expected action was itself invalid
// (a tool not offered, a missing argument), so not a usable test.
Skipped int `json:"skipped"`
ByReason map[string]int `json:"byReason"`
Cases []StepCase `json:"-"`
}
HarvestResult is the harvested cases plus what was looked at.
func Harvest ¶ added in v0.3.0
func Harvest(ctx context.Context, src PairSource, o HarvestOptions) (*HarvestResult, error)
Harvest reads the pair store, selects steps and returns them as redacted cases. Any redaction failure fails the whole harvest: nothing partial.
func (*HarvestResult) Summary ¶ added in v0.3.0
func (h *HarvestResult) Summary() []CaseSummary
Summary lists the cases by provenance and expected tool names only.
type HistoryEntry ¶
type HistoryEntry struct {
Time time.Time `json:"time"`
Kind string `json:"kind"` // offline | online
Suite string `json:"suite,omitempty"`
Gate *Gate `json:"gate,omitempty"`
Variants []VariantScore `json:"variants,omitempty"`
Online *OnlineResult `json:"online,omitempty"`
}
HistoryEntry is one line of the scorecard history (JSONL): an offline suite run or an online reading, appended so trends survive across runs.
func OfflineHistory ¶
func OfflineHistory(sc *Scorecard, at time.Time) HistoryEntry
OfflineHistory summarises sc as a history entry (trials omitted).
type Job ¶
type Job struct {
Task *Task
Variant Variant
// Settings is the host path of the rendered managed settings (may be empty)
// and SettingsDest where it must appear in the environment.
Settings string
SettingsDest string
}
Job is one (task x variant x repeat) trial to set up an environment for.
type Judge ¶
type Judge struct {
LLM LLM
Model string
Cache *JudgeCache // may be nil
}
Judge grades text against a rubric with a pinned model.
func NewJudge ¶
func NewJudge(c *JudgeConfig, suiteDir string) (*Judge, error)
NewJudge builds the judge for c (nil config = no judge). The credential is read from c.APIKeyEnv at call time of this function only.
func (*Judge) Grade ¶
Grade scores parts against r. Cached by (rubric id, version, file hash, model, prompt version, content hash), never by the per-call nonce; only verdicts that parsed are cached, so a schema failure is retried next time rather than frozen, and every hit is re-validated against r.
type JudgeCache ¶
type JudgeCache struct {
Dir string
// contains filtered or unexported fields
}
JudgeCache memoises verdicts in memory and, when Dir is set, on disk (one JSON file per key) so reruns do not pay for the same judgement twice.
type JudgeConfig ¶
type JudgeConfig struct {
URL string `yaml:"url" json:"url"` // gateway base URL; /v1/messages or /v1/responses is appended
Wire string `yaml:"wire" json:"wire"` // anthropic-messages | openai-responses
Model string `yaml:"model" json:"model"` // exact, pinned model id (no "latest" aliases)
// APIKeyEnv names the env var holding the gateway credential.
APIKeyEnv string `yaml:"api_key_env,omitempty" json:"apiKeyEnv,omitempty"`
// Cache is a directory (relative to the suite) for cached verdicts.
Cache string `yaml:"cache,omitempty" json:"cache,omitempty"`
}
JudgeConfig is a suite's `judge:` block: a pinned model reached through the configured gateway.
type JudgeInfo ¶
type JudgeInfo struct {
Model string `json:"model"`
Wire string `json:"wire,omitempty"`
Rubrics []string `json:"rubrics"`
}
JudgeInfo pins what graded the run: the judge model and rubric id@version.
type LLM ¶
type LLM interface {
Complete(ctx context.Context, req LLMRequest) (string, error)
}
LLM sends one prompt and returns the reply text.
type LLMRequest ¶
LLMRequest is one judge completion.
type LocalRunner ¶
type LocalRunner struct{}
LocalRunner runs in temp directories on the host. It provides no isolation beyond a private copy of the repo: use it for tests, not untrusted agents. The settings file is copied next to the repo and its path exported to commands as HALO_SETTINGS_FILE.
type Matrix ¶
type Matrix struct {
// Baseline is the control cell name; default the first cell.
Baseline string `yaml:"baseline,omitempty" json:"baseline,omitempty"`
Harnesses []MatrixHarness `yaml:"harnesses" json:"harnesses"`
Providers []MatrixProvider `yaml:"providers,omitempty" json:"providers,omitempty"`
}
Matrix is one suite run across harness x model x provider. Each harness lists the models it can drive (codex does not run a Claude alias), and a provider either applies to every model under its own alias or, when it has Aliases, only to the models it maps (e.g. bedrock: {sonnet: sonnet-bedrock}). Providers are realised as gateway model aliases: the gateway routes each alias to its upstream, so the CLI side stays provider-agnostic.
type MatrixHarness ¶
type MatrixHarness struct {
Harness string `yaml:"harness" json:"harness"`
Version string `yaml:"version" json:"version"`
Models []string `yaml:"models" json:"models"`
Settings string `yaml:"settings,omitempty" json:"settings,omitempty"`
}
MatrixHarness is one CLI at a pinned version and the models to run it on.
type MatrixProvider ¶
type MatrixProvider struct {
Name string `yaml:"name" json:"name"`
Aliases map[string]string `yaml:"aliases,omitempty" json:"aliases,omitempty"`
}
MatrixProvider names an upstream. Aliases maps model -> gateway alias for that provider; empty means the model alias itself.
type MetricDelta ¶
type MetricDelta struct {
Mean float64 `json:"mean"`
CILo float64 `json:"ciLo"`
CIHi float64 `json:"ciHi"`
Baseline float64 `json:"baseline"`
}
MetricDelta is a paired (by task) bootstrap estimate of candidate - baseline for one metric; Baseline is the baseline's mean per-task value.
type OTLPExporter ¶
type OTLPExporter struct {
URL string // collector base URL; /v1/metrics is appended
Token string // bearer token (optional)
HTTP *http.Client
}
OTLPExporter posts halo.eval.* metrics to an OTel collector over OTLP/HTTP JSON. Point it at the collector's eval receiver (`halo telemetry collector-config --eval-receiver`, 4320, token HALO_OTLP_EVAL_TOKEN) so readings carry halo.source=eval. Never give it the gateway token: rows on the gateway receiver are stamped halo.source=gateway, which the controller trusts for auto-kill. The CLI receiver works too, but its rows are client-controlled (halo.source=cli) and are not shown as evidence.
func (*OTLPExporter) Export ¶
func (x *OTLPExporter) Export(ctx context.Context, rs []OnlineResult, start, now time.Time) error
Export sends one data point per metric per result, stamped at now.
type OnlineOptions ¶
type OnlineOptions struct {
Judge *Judge
Rubric *Rubric
// Sample caps graded pairs per experiment (0 = all); the sample is a
// seeded shuffle, so a rerun with the same seed grades the same pairs.
Sample int
Seed uint64
// Experiment limits grading to one experiment ("" = all).
Experiment string
// Skip holds pair ids already graded (from the history file). It limits
// the judge only: the step-level reading needs every step of a session.
Skip map[string]bool
// AgentSkip holds step pair ids whose agent samples were already exported.
AgentSkip map[string]bool
// AgentQuiet: a step with no recovered production action is exported only
// once it is this old (its next step may still arrive). Default
// DefaultAgentQuiet.
AgentQuiet time.Duration
Now func() time.Time
// ControlNames maps experiment -> its control variant name (from policy).
// halo-shadow does not label the control side, and analysis matches arms by
// the experiment's variant names; default "control".
ControlNames map[string]string
}
OnlineOptions configures one online grading pass.
type OnlineResult ¶
type OnlineResult struct {
Experiment string `json:"experiment"`
Rubric string `json:"rubric"`
JudgeModel string `json:"judgeModel"`
Pairs int `json:"pairs"` // eligible pairs seen
Graded int `json:"graded"` // pairs with both sides judged
JudgeErrors int `json:"judgeErrors"`
Control float64 `json:"controlScore"`
Candidate float64 `json:"candidateScore"`
// Delta is candidate - control score, paired by pair, with a bootstrap CI.
Delta MetricDelta `json:"delta"`
WinRate float64 `json:"winRate"` // share of pairs the candidate scored higher (ties count half)
// ControlVariant/CandidateVariant name the arms (shadow target variants).
ControlVariant string `json:"controlVariant"`
CandidateVariant string `json:"candidateVariant"`
PairIDs []string `json:"pairIds"`
// Scores are the per-pair readings the exporter sends; history keeps them
// (pair ids and numbers only) for harvest's judge-loss selection.
Scores []PairScore `json:"scores,omitempty"`
// Ops are the operational readings over every eligible pair, errored ones
// included (they are the point): the refusal, latency and cost regressions
// ADR-0004 says shadow catches beside quality.
Ops OpsResult `json:"ops"`
// Agent is the step-level reading (ADR-0014): next-action agreement,
// fidelity to production's action, tool-call validity. Nil when no pair's
// actions parsed.
Agent *AgentResult `json:"agent,omitempty"`
// AgentPairIDs are the steps whose agent samples this run exports (each
// step exactly once across runs: the history records them).
AgentPairIDs []string `json:"agentPairIds,omitempty"`
// AgentSteps are those samples (not kept in history).
AgentSteps []StepSample `json:"-"`
}
OnlineResult is one experiment's online quality reading.
func RunOnline ¶
func RunOnline(ctx context.Context, src PairSource, o OnlineOptions) ([]OnlineResult, error)
RunOnline samples pairs per experiment and grades both sides with the rubric. Pairs where either side errored are skipped (that is a reliability signal the gateway metrics already carry, not a quality one). A judge error on either side drops the pair from the scores and is counted.
type OpsResult ¶
type OpsResult struct {
Pairs int `json:"pairs"`
Control ArmStats `json:"control"`
Candidate ArmStats `json:"candidate"`
}
OpsResult is one experiment's per-arm operational reading.
type PairScore ¶
type PairScore struct {
ID string `json:"id"`
Session string `json:"-"`
Control float64 `json:"control"`
Candidate float64 `json:"candidate"`
}
PairScore is one pair's judge scores; each is one analysis sample of halo.eval.judge.score for its arm, under unit = hashed session (pairs of one session are correlated; the analysis takes the per-session mean). History keeps the id and scores, never the raw session, so `halo eval harvest` can select the pairs where the candidate lost.
type PairSource ¶
PairSource yields halo-shadow pairs.
type Part ¶
Part is one section of a judge prompt. Untrusted parts (anything written by the agent or model under evaluation, or by a client) are fenced as data.
type Provenance ¶ added in v0.3.0
type Provenance struct {
PairID string `json:"pairId"`
Experiment string `json:"experiment"`
Session string `json:"session"`
Depth int `json:"depth"`
HarvestedAt time.Time `json:"harvestedAt"`
RedactionRules string `json:"redactionRules"`
}
Provenance says where a case came from. Session is hashed (SessionUnit): a raw session id is never written.
type Rate ¶ added in v0.3.0
type Rate struct {
Mean float64 `json:"mean"`
CILo float64 `json:"ciLo"`
CIHi float64 `json:"ciHi"`
N int `json:"n"`
}
Rate is a proportion with a session-clustered bootstrap CI.
type Redactor ¶ added in v0.3.0
type Redactor struct {
// contains filtered or unexported fields
}
Redactor rewrites a shadow step so it can be written to disk.
func NewRedactor ¶ added in v0.3.0
NewRedactor builds the redactor: the built-in rules plus operator regexes (RE2; every match becomes [REDACTED:custom]) and extra top-level request fields to keep on top of DefaultKeepFields.
func (*Redactor) Action ¶ added in v0.3.0
Action redacts the arguments of every call. JSON arguments are walked and re-encoded in canonical form (sorted keys, as argCanon produces); freeform input (custom tools) is redacted as text.
func (*Redactor) Request ¶ added in v0.3.0
Request keeps only the allowlisted top-level fields of a model request and redacts every string in them. Any failure is an error, never a pass-through.
type Rubric ¶
type Rubric struct {
ID string `yaml:"id" json:"id"`
Version string `yaml:"version" json:"version"`
Instructions string `yaml:"instructions" json:"instructions"`
Criteria []Criterion `yaml:"criteria" json:"criteria"`
// PassThreshold is the weighted score (0..1) at or above which a judge
// grader passes; default 0.7.
PassThreshold float64 `yaml:"pass_threshold,omitempty" json:"passThreshold"`
// contains filtered or unexported fields
}
Rubric is a versioned grading rubric. Bump Version whenever the meaning changes; scorecards record id@version.
func LoadRubric ¶
LoadRubric reads and validates a rubric YAML file.
type RunOptions ¶
type RunOptions struct {
Parallel int
// Judge grades tasks' judge graders; required if any task has one.
Judge *Judge
}
RunOptions tunes RunSuiteWith.
type Scorecard ¶
type Scorecard struct {
Suite string `json:"suite"`
Control string `json:"control"`
Variants []VariantScore `json:"variants"`
Comparisons []Comparison `json:"comparisons"`
Trials []Trial `json:"trials"`
Seed uint64 `json:"seed"`
K int `json:"k,omitempty"`
// Gate is the worst comparison gate; nil with a single variant.
Gate *Gate `json:"gate,omitempty"`
Thresholds *Thresholds `json:"thresholds,omitempty"`
Judge *JudgeInfo `json:"judge,omitempty"`
}
Scorecard is the full result of a suite run.
func BuildScorecard ¶
BuildScorecard aggregates trials. The seed makes bootstrap CIs reproducible.
type StepCase ¶ added in v0.3.0
type StepCase struct {
ID string `json:"id"`
Protocol string `json:"protocol"`
Request json.RawMessage `json:"request"` // redacted
// Tools are the offered tools: name -> required argument keys (null for
// freeform and server tools).
Tools map[string][]string `json:"tools"`
Expected Action `json:"expected"`
ExpectedFrom string `json:"expectedFrom"`
// Alternatives are also accepted: the control replay, when it differed
// from production's action but was valid (the incumbent's own choice).
Alternatives []Action `json:"alternatives,omitempty"`
Match string `json:"match"`
Reasons []string `json:"reasons"`
Provenance Provenance `json:"provenance"`
}
StepCase is one harvested step: the request the coding agent really sent, the tools it offered and the action to reproduce. Graded by GradeStep.
func LoadStepCases ¶ added in v0.3.0
LoadStepCases reads every *.json case in dir, sorted by file name.
type StepModel ¶ added in v0.3.0
type StepModel struct {
URL string // base URL; https, or http to loopback only
Model string
APIKey string
HTTP *http.Client // default: 300s timeout
}
StepModel replays harvested steps against one model route: a gateway (the model is then a gateway alias) or an upstream (the model is its id). The request goes out on the case's own wire protocol, non-streaming, through the same rewrite halo-shadow uses (gateway.ForceNonStreaming, gateway.RewriteRequest).
type StepResult ¶ added in v0.3.0
type StepResult struct {
ID string `json:"id"`
Session string `json:"session"`
Depth int `json:"depth"`
Pass bool `json:"pass"`
SameTools bool `json:"sameTools"`
SameCalls bool `json:"sameCalls"`
Valid bool `json:"valid"`
// PrematureStop: the expected action kept working and the model stopped.
PrematureStop bool `json:"prematureStop"`
Actual Action `json:"actual"`
Error string `json:"error,omitempty"`
JudgeScore *float64 `json:"judgeScore,omitempty"`
JudgeError string `json:"judgeError,omitempty"`
// contains filtered or unexported fields
}
StepResult is one case's grade.
func GradeStep ¶ added in v0.3.0
func GradeStep(c StepCase, a Action) StepResult
GradeStep is the deterministic action matcher: the answer matches when it is SameTools (MatchTools) or SameCalls (MatchCalls) with the expected action or any allowed alternative, and passes when it also calls only offered tools with their required arguments.
type StepRunOptions ¶ added in v0.3.0
type StepRunOptions struct {
Parallel int // concurrent model calls (default 4)
Seed uint64
// Judge and Rubric, both or neither: a secondary rubric score per answered
// case. It never changes Pass.
Judge *Judge
Rubric *Rubric
}
StepRunOptions configures RunSteps.
type StepSample ¶ added in v0.3.0
type StepSample struct {
PairID, Session string
Time time.Time
// HasRef: production's action was recovered; the fidelity fields are set
// only then.
HasRef bool
ControlFidelity, CandFid float64
ControlInvalid, CandInvalid float64 // 1 when the arm's call was invalid
}
StepSample is one step's per-arm reading. It is exported once (tracked by pair id in the history) under unit = hashed session, so the per-unit mean the analysis takes is a per-session mean over its steps, and a rerun never re-weights a session.
type StepScorecard ¶ added in v0.3.0
type StepScorecard struct {
Suite string `json:"suite"`
Model string `json:"model"`
Cases int `json:"cases"`
Sessions int `json:"sessions"`
Errors int `json:"errors"`
// Pass counts an errored case as failed; the other rates are over
// answered cases (PrematureStop: those whose expected action continued).
Pass Rate `json:"pass"`
SameTools Rate `json:"sameTools"`
SameCalls Rate `json:"sameCalls"`
Valid Rate `json:"valid"`
PrematureStop Rate `json:"prematureStop"`
Rubric string `json:"rubric,omitempty"`
Judge *Rate `json:"judge,omitempty"`
JudgeErrors int `json:"judgeErrors,omitempty"`
Results []StepResult `json:"results"`
}
StepScorecard is a step suite's reading against one model. Every interval is a session-clustered bootstrap: steps of one session are correlated.
func RunSteps ¶ added in v0.3.0
func RunSteps(ctx context.Context, suite string, cases []StepCase, m *StepModel, o StepRunOptions) (*StepScorecard, error)
RunSteps replays every case against m and scores the answers.
func (*StepScorecard) Table ¶ added in v0.3.0
func (sc *StepScorecard) Table() string
Table renders the scorecard for a terminal.
type Suite ¶
type Suite struct {
Name string `yaml:"name" json:"name"`
// Tasks are task directory names under <suite dir>/../tasks.
Tasks []string `yaml:"tasks" json:"tasks"`
// Control names the baseline variant; defaults to the first.
Control string `yaml:"control,omitempty" json:"control"`
Repeats int `yaml:"repeats,omitempty" json:"repeats"`
Variants []Variant `yaml:"variants" json:"variants"`
// Seed is the base seed for per-trial seeds and bootstrap CIs. No pinned
// CLI exposes a sampling seed, so it makes the run order and statistics
// reproducible, not the agent itself.
Seed uint64 `yaml:"seed,omitempty" json:"seed,omitempty"`
// K is the k of pass@k and pass^k; default Repeats.
K int `yaml:"k,omitempty" json:"k,omitempty"`
// Gate holds the ship/hold/block thresholds.
Gate Thresholds `yaml:"gate,omitempty" json:"gate"`
// Judge configures the LLM-as-judge grader used by tasks' judge graders.
Judge *JudgeConfig `yaml:"judge,omitempty" json:"judge,omitempty"`
// Matrix, used by `halo eval run --matrix`, replaces Variants with the
// harness x model x provider cross product.
Matrix *Matrix `yaml:"matrix,omitempty" json:"matrix,omitempty"`
// Graders apply to every task in addition to its own (e.g. a judge
// rubric for the whole suite, so shared tasks stay judge-free).
Graders []GraderSpec `yaml:"graders,omitempty" json:"graders,omitempty"`
// Dir is the directory of the suite file; set by LoadSuite.
Dir string `yaml:"-" json:"-"`
}
Suite lists tasks and the variants to compare.
func (*Suite) LoadTasks ¶
LoadTasks loads the suite's tasks from tasksRoot (default <suite dir>/../tasks).
func (*Suite) WithMatrix ¶
WithMatrix returns a copy of s whose variants are the matrix cells and whose control is the matrix baseline.
type Task ¶
type Task struct {
ID string `yaml:"id" json:"id"`
Repo string `yaml:"repo" json:"repo"` // git URL, or directory relative to the task dir
Setup []string `yaml:"setup,omitempty" json:"setup,omitempty"`
Prompt string `yaml:"prompt" json:"prompt"`
Check string `yaml:"check" json:"check"` // shell command, exit 0 = pass
Timeout time.Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
BudgetUSD float64 `yaml:"budget_usd,omitempty" json:"budgetUSD,omitempty"`
MaxTurns int `yaml:"max_turns,omitempty" json:"maxTurns,omitempty"`
Tags []string `yaml:"tags,omitempty" json:"tags,omitempty"`
// Graders all have to pass too; check may be empty when graders are set.
Graders []GraderSpec `yaml:"graders,omitempty" json:"graders,omitempty"`
// Dir is the directory holding task.yaml; set by LoadTask.
Dir string `yaml:"-" json:"-"`
}
Task is one eval task.
type TaskDiff ¶
type TaskDiff struct {
Task string `json:"task"`
Baseline float64 `json:"baseline"` // pass rate over trials
Candidate float64 `json:"candidate"` // pass rate over trials
Status string `json:"status"` // regressed | improved | same | flaky
Reason string `json:"reason,omitempty"`
}
TaskDiff is one task's per-arm pass rates.
type Thresholds ¶
type Thresholds struct {
// MaxPassDrop is the non-inferiority margin on the paired per-task pass
// rate: ship needs the CI lower bound >= -MaxPassDrop. Default 0.05.
MaxPassDrop *float64 `yaml:"max_pass_drop,omitempty" json:"maxPassDrop,omitempty"`
MaxCostIncrease *float64 `yaml:"max_cost_increase,omitempty" json:"maxCostIncrease,omitempty"`
MaxTokensIncrease *float64 `yaml:"max_tokens_increase,omitempty" json:"maxTokensIncrease,omitempty"`
MaxWallIncrease *float64 `yaml:"max_wall_increase,omitempty" json:"maxWallIncrease,omitempty"`
MaxToolCallsIncrease *float64 `yaml:"max_tool_calls_increase,omitempty" json:"maxToolCallsIncrease,omitempty"`
// TaskRegression is the per-task pass-rate drop that marks a task as
// regressed (default 0.5); more than MaxRegressedTasks (default 0) blocks.
TaskRegression float64 `yaml:"task_regression,omitempty" json:"taskRegression"`
MaxRegressedTasks int `yaml:"max_regressed_tasks,omitempty" json:"maxRegressedTasks"`
// MinTasks is the fewest non-flaky tasks a verdict other than hold needs
// (default 1).
MinTasks int `yaml:"min_tasks,omitempty" json:"minTasks"`
}
Thresholds is a suite's `gate:` block. Pass-rate margins are absolute (0.05 = 5 percentage points); cost/tokens/wall/tool-call limits are relative increases over the baseline mean (0.2 = +20%) and are only gated when set.
type TokenUsage ¶ added in v0.4.0
type TokenUsage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
}
TokenUsage is the token count the provider reported for one completion; zero when the reply carried no usage block.
type Trial ¶
type Trial struct {
Task string `json:"task"`
Variant string `json:"variant"`
Repeat int `json:"repeat"`
Pass bool `json:"pass"`
CostUSD float64 `json:"costUSD"`
Turns int `json:"turns"`
WallMs int64 `json:"wallMs"`
Tokens int64 `json:"tokens"`
ToolErrors int `json:"toolErrors"`
ToolCalls int `json:"toolCalls"`
// Seed is derived from the suite seed, task and repeat (identical across
// variants, so arms stay paired). Recorded; no pinned CLI accepts one.
Seed uint64 `json:"seed,omitempty"`
// Grades are the task's graders' results (absent for check-only tasks).
Grades []Grade `json:"grades,omitempty"`
// (ErrSandboxUnavailable): the trial never ran, is excluded from every
// statistic, and makes the gate hold. Never a failure.
Unavailable string `json:"unavailable,omitempty"`
// GraderError is set when a grader could not produce a verdict (e.g. the
// judge's reply failed schema validation). The trial does not pass.
GraderError string `json:"graderError,omitempty"`
// Error records setup/agent/parse problems. The trial is still scored by
// the check command where possible; infra failures count as not passed.
Error string `json:"error,omitempty"`
}
Trial is the scored outcome of one (task x variant x repeat) run.
func RunSuite ¶
func RunSuite(ctx context.Context, s *Suite, tasks []*Task, r Runner, drivers map[string]Driver, parallel int) ([]Trial, error)
RunSuite runs every task x variant x repeat with up to parallel concurrent trials and returns trials in deterministic order. It returns ctx.Err() if cancelled before finishing.
func RunSuiteWith ¶
func RunSuiteWith(ctx context.Context, s *Suite, tasks []*Task, r Runner, drivers map[string]Driver, o RunOptions) ([]Trial, error)
RunSuiteWith is RunSuite with a judge for rubric graders.
type Usage ¶
type Usage struct {
CostUSD float64
Turns int
DurationMs int64
Tokens int64 // input + output (+ cache) tokens
ToolErrors int
ToolCalls int
Failed bool // harness reported the run as failed/errored
Error string // message when Failed
}
Usage is what a driver extracts from a headless run's output.
type Variant ¶
type Variant struct {
Name string `yaml:"name" json:"name"`
Harness string `yaml:"harness" json:"harness"` // claude | codex | gemini
Version string `yaml:"version" json:"version"`
Model string `yaml:"model" json:"model"` // gateway model alias
// Provider labels the upstream the alias routes to (matrix runs); recorded only.
Provider string `yaml:"provider,omitempty" json:"provider,omitempty"`
// Settings is a path (relative to the suite file) to the rendered managed
// settings for this variant, e.g. the output of `halo render`.
Settings string `yaml:"settings,omitempty" json:"settings,omitempty"`
// Dir is the suite dir Settings must live in; set by LoadSuite.
Dir string `yaml:"-" json:"-"`
}
Variant is one arm of a suite: a harness at a version on a model, with an optional rendered managed-settings file mounted into the environment.
type VariantScore ¶
type VariantScore struct {
Name string `json:"name"`
Trials int `json:"trials"`
Pass1 float64 `json:"pass1"`
Pass1CILo float64 `json:"pass1CILo"`
Pass1CIHi float64 `json:"pass1CIHi"`
CostPerTask float64 `json:"costPerTaskUSD"` // mean cost per trial
WallP50Ms int64 `json:"wallP50Ms"`
WallP95Ms int64 `json:"wallP95Ms"`
Tokens int64 `json:"tokens"`
ToolErrors int `json:"toolErrors"`
Errors int `json:"errors"` // trials with a recorded Error
Harness string `json:"harness,omitempty"`
Version string `json:"version,omitempty"`
Model string `json:"model,omitempty"`
Provider string `json:"provider,omitempty"`
// PassAtK / PassHatK are the per-task unbiased pass@k (any of k trials
// passes) and pass^k (all k pass), averaged over tasks.
K int `json:"k,omitempty"`
PassAtK float64 `json:"passAtK"`
PassHatK float64 `json:"passHatK"`
ToolCalls int `json:"toolCalls"`
GradeErrors int `json:"gradeErrors"`
JudgeScore *float64 `json:"judgeScore,omitempty"` // mean judge score over judged trials
// Unavailable counts trials this host could not measure (Trial.Unavailable,
// e.g. codex without Landlock). They are excluded from every statistic; a
// variant with no measured trials shows UnavailableReason, not 0%.
}
VariantScore aggregates one variant's trials.