eval

package
v0.43.5 Latest Latest
Warning

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

Go to latest
Published: Aug 19, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package eval stores eval task sets and runs comparison evals against a base agent's live engine under the no-side-effects execution policy (agent.ExecPolicy). Stages B and C of design/eval-subsystem.md: L2 task sets, the L3 runner and its objective metrics, then the blinded pairing, judging and decision rule layered on top.

Index

Constants

View Source
const (
	CategoryChat         = "chat"
	CategorySkillCommand = "skill_command"
	CategoryScheduled    = "scheduled"
	CategoryToolHeavy    = "tool_heavy"
)

Task categories. The curation axis from design/eval-subsystem.md §4.3 — validated in Go rather than by a SQL CHECK constraint, matching the house style (there are no CHECK constraints anywhere in the schema).

View Source
const (
	StatusPending = "pending"
	StatusRunning = "running"
	StatusDone    = "done"
	StatusCapped  = "capped"
	StatusStopped = "stopped"
	StatusFailed  = "failed"
)

Run statuses.

View Source
const (
	SampleOK     = "ok"
	SampleFailed = "failed"
)

Sample statuses. A sample is the unit of failure tolerance: a provider hiccup fails the sample, never the run.

View Source
const (
	ItemPending = "pending"
	ItemJudged  = "judged"
)

Judgment item statuses.

View Source
const (
	OrderAB = "ab"
	OrderBA = "ba"
)

Presentation orders. An item's order says which pair letter is shown to the judge first: "ab" shows the pair's A sample as Response A, "ba" swaps them. Two items per pair, one of each, is what makes position-bias control structural rather than a matter of judge discipline.

View Source
const (
	WinnerA   = "a"
	WinnerB   = "b"
	WinnerTie = "tie"
)

Verdict winners, in terms of the *presented* responses — the judge never learns the pair letters, only "Response A" and "Response B" as shown to it.

View Source
const (
	DimTaskSuccess = "task_success"
	DimToolPath    = "tool_path"
	DimPersonaFit  = "persona_fit"
	DimLength      = "length"
)

Judgment dimensions, in the order the rubric lists them.

View Source
const (
	VerdictUpgrade       = "upgrade"
	VerdictDowngrade     = "downgrade"
	VerdictNoRegressions = "no_regressions"
	VerdictInconclusive  = "inconclusive"
)

Decision-rule outcomes.

The rule is asymmetric on purpose: the objective gates alone can declare a downgrade (a gate failed — no judge needed to reject a candidate) or report that nothing regressed, but they can never declare an upgrade. That needs the judge win-rate, so a candidate cannot be promoted on the strength of being cheap and quiet.

View Source
const (
	GateRejectedRate = "rejected_rate"
	GateMeanRounds   = "mean_rounds"
	GateCostPerTask  = "mean_cost_per_task"
)

Gate names, as they appear in the gate table.

View Source
const JudgeOperator = "operator"

JudgeOperator is the judge identity reserved for the operator's calibration marks. Stored as ordinary verdicts (no schema of their own) and excluded from the win rate; they only feed the operator–judge agreement figure.

Variables

View Source
var ErrNameTaken = errors.New("eval: task set name already exists")

ErrNameTaken is returned when a task-set name collides with an existing one.

View Source
var ErrNotFound = errors.New("eval: not found")

ErrNotFound is the sentinel every lookup wraps when the addressed row does not exist, so REST handlers can classify a 404 with errors.Is rather than by inspecting the message (the tool.ErrToolNotFound convention).

View Source
var ErrRunNotActive = errors.New("eval: run is not active")

ErrRunNotActive is returned by handlers that tried to stop a run that is already terminal.

View Source
var ErrTaskSetInUse = errors.New("eval: task set is referenced by runs")

ErrTaskSetInUse is returned by DeleteTaskSet when runs still reference the set. Deleting would either orphan those runs or cascade away results the operator may still be reading, so the delete is refused and the caller maps it to 409.

Functions

func Categories

func Categories() []string

Categories returns the four valid task categories, in the order the docs list them.

func Dimensions

func Dimensions() []string

Dimensions returns the four judgment dimensions, in rubric order.

func IsTerminal

func IsTerminal(status string) bool

IsTerminal reports whether a run status is final — nothing further will be dispatched and the row will not change again.

func SampleConvID

func SampleConvID(runID, taskID int64, k int, variantID int64) string

SampleConvID mints the in-flight identity for one sample. The variant is part of it, not just run/task/k: the identity doubles as the cost tracker's session key, and two variants of the same (task, k) sharing one key would bill the second for the first's spend — enough to trip the cap early and to report a per-sample cost that is really a running total. It is also what makes the audit log's session grouping genuinely per-sample.

func ValidCategory

func ValidCategory(c string) bool

ValidCategory reports whether c is one of the four curation categories.

func ValidWinner

func ValidWinner(w string) bool

ValidWinner reports whether w is one of the three verdict outcomes.

func VariantFor

func VariantFor(a Assignment, order, winner string) int64

VariantFor resolves a presented winner letter back to the variant that produced it, given the item's presentation order. Returns 0 for a tie.

Two indirections, deliberately: the judge names a presented letter, the item says whether that letter was the pair's own A or B, and only the assignment says which variant that was. Nothing short of the pair row can unblind a verdict.

Types

type Agreement

type Agreement struct {
	Items  int     `json:"items"`
	Agreed int     `json:"agreed"`
	Rate   float64 `json:"rate"`
}

Agreement is the operator–judge calibration figure: how often the operator's own call on a calibration item matched the judge's. Below roughly 80 % the rubric wants fixing before headless judging is trusted, since a drifted rubric silently devalues every later run.

type Assignment

type Assignment struct {
	A int64 `json:"a"`
	B int64 `json:"b"`
}

Assignment is the decoded eval_pairs.assignment JSON: which variant each presented letter really was.

func DecodeAssignment

func DecodeAssignment(raw string) (Assignment, error)

DecodeAssignment parses a pair's unblinding key.

type BlindedItem

type BlindedItem struct {
	ItemID   int64  `json:"item_id"`
	RunID    int64  `json:"run_id"`
	TaskID   int64  `json:"task_id"`
	Prompt   string `json:"prompt"`
	Category string `json:"category"`
	// Notes is the task's free-text "what good looks like". Judge context, not
	// an assertion — nothing parses it.
	Notes string `json:"notes,omitempty"`
	// PinnedHistory is the context the turn ran against, so a verdict can tell
	// a non-sequitur from a correct follow-up.
	PinnedHistory json.RawMessage `json:"pinned_history,omitempty"`
	Status        string          `json:"status"`
	ResponseA     BlindedResponse `json:"response_a"`
	ResponseB     BlindedResponse `json:"response_b"`
}

BlindedItem is the judge-visible payload for one judgment item.

type BlindedResponse

type BlindedResponse struct {
	Response   string            `json:"response"`
	Rounds     int               `json:"rounds"`
	StopReason string            `json:"stop_reason,omitempty"`
	ToolCalls  []BlindedToolCall `json:"tool_calls"`
}

BlindedResponse is one side of a pair. Deliberately absent: variant name, model, provider, token usage, cost, latency, and the sample's conversation id (which names the variant). Duration is dropped too — a consistently slower side is an identity hint.

type BlindedToolCall

type BlindedToolCall struct {
	Round     int    `json:"round"`
	Name      string `json:"tool_name"`
	Server    string `json:"server_name,omitempty"`
	Outcome   string `json:"outcome"`
	Arguments string `json:"arguments,omitempty"`
	Result    string `json:"result,omitempty"`
	Error     string `json:"error,omitempty"`
}

BlindedToolCall is one tool call as the judge sees it. Built field by field from agent.ToolCallRecord rather than embedding it, so a field added to the record cannot leak into a judge payload by default.

type CategoryResult

type CategoryResult struct {
	Category    string  `json:"category"`
	JudgedPairs int     `json:"judged_pairs"`
	Wins        int     `json:"wins"`
	Losses      int     `json:"losses"`
	Ties        int     `json:"ties"`
	WinRate     float64 `json:"win_rate"`
	// Deltas mirror the three gates, restricted to this category's tasks.
	DeltaRejectedPP float64 `json:"delta_rejected_pp"`
	DeltaRoundsPct  float64 `json:"delta_rounds_pct"`
	DeltaCostPct    float64 `json:"delta_cost_pct"`
	// Regressed is true when this category alone would fail a gate or fall
	// below the win threshold, whatever the aggregate says.
	Regressed bool `json:"regressed"`
}

CategoryResult breaks a candidate's performance down by task category. A rolled-up number hides bidirectional failures: a candidate winning big on chat while losing on tool-heavy still shows a comfortable overall win, and tool-heavy is usually what the operator actually cares about.

type Completeness

type Completeness struct {
	SamplesOK       int     `json:"samples_ok"`
	SamplesExpected int     `json:"samples_expected"`
	Ratio           float64 `json:"ratio"`
	Floor           float64 `json:"floor"`
	Conclusive      bool    `json:"conclusive"`
	// Pairs and PairsJudged sit next to the sample figures because a run can be
	// sample-complete and still have holes in the judging grid: a (task, k)
	// whose sample failed on either side yields no pair at all.
	Pairs       int `json:"pairs"`
	PairsJudged int `json:"pairs_judged"`
}

Completeness reports how much of the run actually landed. A run that finishes below the floor still reports its numbers — partial results are the point of the capped and stopped statuses — but says they are inconclusive rather than dressing thin data as a verdict.

type Config

type Config struct {
	MaxConcurrent     int
	MaxCostPerRun     float64
	DefaultK          int
	CompletenessFloor float64
	AuditMode         string
}

Config is the runner's snapshot of eval, resolved once at construction.

type Engine

type Engine interface {
	// DryRun executes one turn under an execution policy and persists nothing.
	DryRun(ctx context.Context, msg adapter.IncomingMessage, policy agent.ExecPolicy) (*agent.TurnResult, error)
	// LLMRouter exposes the cost tracker (real per-sample spend) and provider
	// registry (overlay validation).
	LLMRouter() *llm.Router
	// Name is the base agent's name, used for the audit pseudo-identity.
	Name() string
}

Engine is the slice of *agent.Engine the runner needs. It exists so the package is testable with a hand-written mock: agent.Engine is a concrete struct with a large constructor.

type EngineSource

type EngineSource func(name string) (Engine, bool)

EngineSource resolves a base agent name to its live engine. main.go adapts Dispatcher.Agent; the nil check there must happen *before* the value is boxed into this interface, or a typed-nil pointer reads as non-nil here.

type Gate

type Gate struct {
	Name     string  `json:"name"`
	Baseline float64 `json:"baseline"`
	Value    float64 `json:"value"`
	Delta    float64 `json:"delta"`
	// Threshold is the largest Delta that still passes, in Unit.
	Threshold float64 `json:"threshold"`
	// Unit is "pp" (percentage points, for rates) or "%" (relative change).
	Unit string `json:"unit"`
	Pass bool   `json:"pass"`
}

Gate is one row of the objective gate table. Every verdict surface shows this table, not just the label: a bare verdict banner with no visible criteria is the black box this subsystem exists to remove.

type ImportError

type ImportError struct {
	Line int
	Err  error
}

ImportError names the offending line so an operator hand-editing a JSONL file is told where to look, not just that something was wrong.

func (*ImportError) Error

func (e *ImportError) Error() string

func (*ImportError) Unwrap

func (e *ImportError) Unwrap() error

type JSONLTask

type JSONLTask struct {
	Prompt               string          `json:"prompt"`
	Category             string          `json:"category"`
	PinnedHistory        json.RawMessage `json:"pinned_history,omitempty"`
	Tags                 json.RawMessage `json:"tags,omitempty"`
	Notes                string          `json:"notes,omitempty"`
	SourceConversationID string          `json:"source_conversation_id,omitempty"`
	SourceMessageID      *int64          `json:"source_message_id,omitempty"`
}

JSONLTask is one line of a task-set export. It is the portable shape: the row's identity and provenance ids are written for reference but ignored on import, so a set exported from one instance imports cleanly into another.

type Judgment

type Judgment struct {
	Pairs int `json:"pairs"`
	// JudgedPairs counts pairs whose *both* presentation orders carry a judge
	// verdict. A half-judged pair is not evidence.
	JudgedPairs  int     `json:"judged_pairs"`
	Wins         int     `json:"wins"`
	Losses       int     `json:"losses"`
	Ties         int     `json:"ties"`
	WinRate      float64 `json:"win_rate"`
	WinThreshold float64 `json:"win_threshold"`
	// OperatorAgreement is nil until the operator marks a calibration item.
	OperatorAgreement *Agreement `json:"operator_agreement,omitempty"`
}

Judgment is the blinded-pair tally for one candidate against the baseline.

type JudgmentItem

type JudgmentItem struct {
	ID                int64     `db:"id"                 json:"id"`
	PairID            int64     `db:"pair_id"            json:"pair_id"`
	PresentationOrder string    `db:"presentation_order" json:"presentation_order"`
	Status            string    `db:"status"             json:"status"`
	CreatedAt         time.Time `db:"created_at"         json:"created_at"`
}

JudgmentItem is one pass over a pair at a fixed presentation order. A pair yields two, one per order.

type Overlay

type Overlay struct {
	Model    string `json:"llm_model,omitempty"`
	Provider string `json:"llm_provider,omitempty"`
}

Overlay is the decoded eval_variants.overlay JSON.

func DecodeOverlay

func DecodeOverlay(raw string) (Overlay, error)

DecodeOverlay parses a variant overlay. An empty or "{}" overlay is the incumbent: it runs the agent's live config unchanged.

type Pair

type Pair struct {
	ID      int64 `db:"id"        json:"id"`
	RunID   int64 `db:"run_id"    json:"run_id"`
	TaskID  int64 `db:"task_id"   json:"task_id"`
	KIndex  int   `db:"k_index"   json:"k_index"`
	SampleA int64 `db:"sample_a"  json:"sample_a"`
	SampleB int64 `db:"sample_b"  json:"sample_b"`
	// Assignment is the letter→variant map. It is the unblinding key and must
	// never reach a judge-visible payload.
	Assignment string    `db:"assignment" json:"assignment"`
	CreatedAt  time.Time `db:"created_at" json:"created_at"`
}

Pair is one blinded comparison: a baseline sample and a candidate sample for the same (task, k), with a random A/B assignment that lives server-side only.

type PendingItem

type PendingItem struct {
	ItemID   int64  `db:"item_id"  json:"item_id"`
	PairID   int64  `db:"pair_id"  json:"pair_id"`
	RunID    int64  `db:"run_id"   json:"run_id"`
	TaskID   int64  `db:"task_id"  json:"task_id"`
	Category string `db:"category" json:"category"`
	// Prompt is the task's own text, which is identical for both sides and so
	// leaks nothing.
	Prompt string `db:"prompt" json:"prompt"`
}

PendingItem is one entry of the judge's queue. It carries just enough to pick work — never the responses, which come from GetBlindedItem.

type ProgressEvent

type ProgressEvent struct {
	RunID        int64   `json:"run_id"`
	Status       string  `json:"status"`
	SamplesDone  int     `json:"samples_done"`
	SamplesTotal int     `json:"samples_total"`
	CostSpent    float64 `json:"cost_spent"`
	CostCap      float64 `json:"cost_cap"`
	ETASeconds   int     `json:"eta_seconds,omitempty"`
}

ProgressEvent is emitted after every sample and at both ends of a run. It is deliberately droppable: main.go forwards it to the WebSocket hub, and GET /eval/runs/{id} is the authoritative fallback.

type Run

type Run struct {
	ID         int64      `db:"id"          json:"id"`
	TaskSetID  int64      `db:"task_set_id" json:"task_set_id"`
	BaseAgent  string     `db:"base_agent"  json:"base_agent"`
	Status     string     `db:"status"      json:"status"`
	K          int        `db:"k"           json:"k"`
	CostCap    float64    `db:"cost_cap"    json:"cost_cap"`
	CostSpent  float64    `db:"cost_spent"  json:"cost_spent"`
	AsOf       time.Time  `db:"as_of"       json:"as_of"`
	Error      string     `db:"error"       json:"error,omitempty"`
	CreatedAt  time.Time  `db:"created_at"  json:"created_at"`
	FinishedAt *time.Time `db:"finished_at" json:"finished_at,omitempty"`
}

Run is one comparison run.

type Runner

type Runner struct {

	// OnProgress is called after each sample and at run start/finish. Nil-safe.
	OnProgress func(ProgressEvent)
	// contains filtered or unexported fields
}

Runner executes eval runs in the background against live engines.

Execution vehicle: samples run on the agent's *live* engine via Engine.DryRun with an ExecEval policy, not on a per-run rebuilt engine. The unit of evaluation is model-in-harness, so the sample must see the agent's real skills, tools, persona and auditor; a capability-reduced or duplicated engine would measure a different system. Isolation comes from ExecPolicy (structural, not filtered) and the variant's router is a per-turn clone, so nothing about the live engine is mutated.

func NewRunner

func NewRunner(store *Store, engines EngineSource, auditor audit.Emitter, cfg Config, logger *slog.Logger) *Runner

NewRunner builds a runner. It starts no goroutine: a user who never launches a run pays nothing beyond the five empty tables.

func (*Runner) Config

func (r *Runner) Config() Config

Config returns the runner's resolved settings, so handlers can report the defaults a run was created against.

func (*Runner) IsActive

func (r *Runner) IsActive(runID int64) bool

IsActive reports whether a run is currently executing.

func (*Runner) Shutdown

func (r *Runner) Shutdown()

Shutdown stops every run and waits for the goroutines to finish.

func (*Runner) StartRun

func (r *Runner) StartRun(ctx context.Context, runID int64) error

StartRun launches a pending run in the background. It returns as soon as the run is registered; progress is observable through the store and OnProgress.

func (*Runner) Stop

func (r *Runner) Stop(runID int64) bool

Stop cancels an active run. Reports whether the run was active — a terminal run has nothing to cancel, and the handler turns that into a 409.

func (*Runner) StopAll

func (r *Runner) StopAll()

StopAll cancels every active run. Wired into the panic switch: an emergency stop halts eval spend along with everything else. Deliberately not paired with a resume — a panic is not a pause, and a stopped run stays stopped.

type Sample

type Sample struct {
	ID         int64  `db:"id"          json:"id"`
	RunID      int64  `db:"run_id"      json:"run_id"`
	VariantID  int64  `db:"variant_id"  json:"variant_id"`
	TaskID     int64  `db:"task_id"     json:"task_id"`
	KIndex     int    `db:"k_index"     json:"k_index"`
	Status     string `db:"status"      json:"status"`
	Error      string `db:"error"       json:"error,omitempty"`
	Response   string `db:"response"    json:"response"`
	Trace      string `db:"trace"       json:"trace"`
	Rounds     int    `db:"rounds"      json:"rounds"`
	StopReason string `db:"stop_reason" json:"stop_reason,omitempty"`
	// Upstream is the provider-reported serving upstream (OpenRouter's routed
	// provider), empty for providers without the concept.
	Upstream string `db:"upstream" json:"upstream,omitempty"`
	// Outcome counts are tool-call level, split exactly as
	// agent.ToolCallRecord.Outcome. Cached and suppressed are kept separate
	// from failed on purpose: folding either in would poison the failed-rate
	// gate, and an eval turn suppresses writes routinely.
	OutcomeOK         int       `db:"outcome_ok"         json:"outcome_ok"`
	OutcomeRejected   int       `db:"outcome_rejected"   json:"outcome_rejected"`
	OutcomeFailed     int       `db:"outcome_failed"     json:"outcome_failed"`
	OutcomeDenied     int       `db:"outcome_denied"     json:"outcome_denied"`
	OutcomeCached     int       `db:"outcome_cached"     json:"outcome_cached"`
	OutcomeSuppressed int       `db:"outcome_suppressed" json:"outcome_suppressed"`
	TokensPrompt      int       `db:"tokens_prompt"      json:"tokens_prompt"`
	TokensCompletion  int       `db:"tokens_completion"  json:"tokens_completion"`
	Cost              float64   `db:"cost"               json:"cost"`
	LatencyMs         int64     `db:"latency_ms"         json:"latency_ms"`
	CreatedAt         time.Time `db:"created_at"         json:"created_at"`
}

Sample is one (task, variant, k) execution.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store persists eval task sets, runs and samples. It owns its own handle on the main database file, following the kv package: the eval tables live in the main DB (§5) because every run reads eval_tasks, and the write rate is far below anything that would justify a separate file.

func NewInMemoryStore

func NewInMemoryStore() (*Store, error)

NewInMemoryStore creates an in-memory SQLite store for testing.

func NewSQLiteStore

func NewSQLiteStore(dbPath string) (*Store, error)

NewSQLiteStore opens or creates the database and applies the eval schema.

func (*Store) AddRunCost

func (s *Store) AddRunCost(ctx context.Context, runID int64, cost float64) error

AddRunCost accumulates spend on a run. Called after every sample so a crashed process still leaves an honest figure behind.

func (*Store) AddSample

func (s *Store) AddSample(ctx context.Context, smp Sample) (*Sample, error)

AddSample inserts one executed sample.

func (*Store) AddTask

func (s *Store) AddTask(ctx context.Context, setID int64, t Task) (*Task, error)

AddTask appends a task to a set.

func (*Store) Close

func (s *Store) Close() error

Close releases the database connection.

func (*Store) CountPairs

func (s *Store) CountPairs(ctx context.Context, runID int64) (int, error)

CountPairs returns how many pairs a run has.

func (*Store) CountSamples

func (s *Store) CountSamples(ctx context.Context, runID int64) (int, error)

CountSamples returns how many samples a run has recorded so far.

func (*Store) CreatePairs

func (s *Store) CreatePairs(ctx context.Context, runID int64) (int, error)

CreatePairs is the run-finalization step that turns completed samples into blinded judgment work. It returns how many pairs it created.

It runs for capped and stopped runs as well as done ones, since partial results are the whole point of those statuses — a run that spent real money before hitting its cap should still be judgeable on what it produced. A (task, k) whose sample is missing or failed on either side yields no pair; the count is reported next to the completeness figure so a reader can see how much of the grid survived.

Pairing policy for more than two variants: every non-baseline variant is paired against the baseline (the first variant by creation order, the same convention per-task deltas use). N−1 pair sets rather than a round-robin, because the question the decision rule answers is "is this candidate an upgrade on the incumbent", and no consumer reads candidate-vs-candidate.

Idempotent by guard: a run that already has pairs is left alone, so a retried finalization cannot double the queue.

func (*Store) CreateRun

func (s *Store) CreateRun(ctx context.Context, run Run, variants []Variant) (*Run, []Variant, error)

CreateRun inserts a run in the pending status together with its variants, in one transaction: a run without variants has nothing to compare and must never be visible.

func (*Store) CreateTaskSet

func (s *Store) CreateTaskSet(ctx context.Context, name, description string) (*TaskSet, error)

CreateTaskSet inserts a task set. A duplicate name returns ErrNameTaken.

func (*Store) DeleteTask

func (s *Store) DeleteTask(ctx context.Context, setID, taskID int64) error

DeleteTask removes one task from a set.

func (*Store) DeleteTaskSet

func (s *Store) DeleteTaskSet(ctx context.Context, name string) error

DeleteTaskSet removes a set and its tasks. It refuses with ErrTaskSetInUse when any run references the set: a run's samples are only interpretable against the tasks that produced them.

func (*Store) ExportJSONL

func (s *Store) ExportJSONL(ctx context.Context, setID int64, w io.Writer) error

ExportJSONL writes one task per line.

func (*Store) FinishRun

func (s *Store) FinishRun(ctx context.Context, runID int64, status, errMsg string) error

FinishRun writes the terminal status, error and finish time in one update.

func (*Store) GetBlindedItem

func (s *Store) GetBlindedItem(ctx context.Context, itemID int64) (*BlindedItem, error)

GetBlindedItem builds the judge-visible payload for one item.

The payload is constructed from scratch rather than filtered out of the stored rows: blinding that works by removing fields fails open the day a column is added, and the whole judge path depends on it failing closed.

func (*Store) GetItem

func (s *Store) GetItem(ctx context.Context, itemID int64) (*JudgmentItem, error)

GetItem returns one judgment item by id.

func (*Store) GetPair

func (s *Store) GetPair(ctx context.Context, pairID int64) (*Pair, error)

GetPair returns one pair by id.

func (*Store) GetRun

func (s *Store) GetRun(ctx context.Context, id int64) (*Run, error)

GetRun returns a run by id.

func (*Store) GetSample

func (s *Store) GetSample(ctx context.Context, id int64) (*Sample, error)

GetSample returns one sample by id.

func (*Store) GetTask

func (s *Store) GetTask(ctx context.Context, setID, taskID int64) (*Task, error)

GetTask returns one task by id, scoped to a set so a caller cannot address another set's task through a set-scoped route.

func (*Store) GetTaskSet

func (s *Store) GetTaskSet(ctx context.Context, name string) (*TaskSet, error)

GetTaskSet returns a task set by name.

func (*Store) GetTaskSetByID

func (s *Store) GetTaskSetByID(ctx context.Context, id int64) (*TaskSet, error)

GetTaskSetByID returns a task set by id.

func (*Store) ImportJSONL

func (s *Store) ImportJSONL(ctx context.Context, setID int64, r io.Reader) (int, error)

ImportJSONL appends every line of r to a set. It is all-or-none: every line is parsed and validated before anything is written, so a typo halfway down a hand-edited file leaves the set exactly as it was rather than half-imported.

func (*Store) ListItems

func (s *Store) ListItems(ctx context.Context, runID int64) ([]JudgmentItem, error)

ListItems returns every judgment item of a run, in creation order.

func (*Store) ListPairs

func (s *Store) ListPairs(ctx context.Context, runID int64) ([]Pair, error)

ListPairs returns a run's pairs in creation order.

func (*Store) ListPending

func (s *Store) ListPending(ctx context.Context, runID int64, limit, sampleN int) ([]PendingItem, error)

ListPending returns pending judgment items, optionally scoped to one run (0 = every run). sampleN > 0 draws a random subset instead of the head of the queue: the interactive calibration pass judges ~20 items, and taking the first 20 would calibrate against whichever tasks happen to sort first.

func (*Store) ListRuns

func (s *Store) ListRuns(ctx context.Context, taskSetID int64, status string) ([]Run, error)

ListRuns returns runs newest first, optionally filtered by task set id (0 = any) and status ("" = any).

func (*Store) ListSamples

func (s *Store) ListSamples(ctx context.Context, runID int64) ([]Sample, error)

ListSamples returns a run's samples in insertion order.

func (*Store) ListTaskSets

func (s *Store) ListTaskSets(ctx context.Context) ([]TaskSet, error)

ListTaskSets returns every task set with its task count, ordered by name.

func (*Store) ListTasks

func (s *Store) ListTasks(ctx context.Context, setID int64) ([]Task, error)

ListTasks returns the tasks of a set, in creation order — the order the runner dispatches them and the order per-task deltas are baselined against.

func (*Store) ListVariants

func (s *Store) ListVariants(ctx context.Context, runID int64) ([]Variant, error)

ListVariants returns a run's variants in creation order. The first is the per-task delta baseline (convention: the incumbent is created first).

func (*Store) ListVerdicts

func (s *Store) ListVerdicts(ctx context.Context, runID int64) ([]Verdict, error)

ListVerdicts returns every verdict recorded against a run's items.

func (*Store) RecordVerdict

func (s *Store) RecordVerdict(ctx context.Context, v Verdict) (*Verdict, error)

RecordVerdict writes one judge's call on one item and marks the item judged.

The operator's calibration marks deliberately do *not* flip the status: they are recorded against an item the judge has already worked, and an item that only the operator has seen is still outstanding judge work.

func (*Store) SetRunStatus

func (s *Store) SetRunStatus(ctx context.Context, runID int64, status, errMsg string) error

SetRunStatus updates a run's status and error text.

func (*Store) Summarize

func (s *Store) Summarize(ctx context.Context, runID int64, opts SummaryOpts) (*Summary, error)

Summarize aggregates a run's samples in Go rather than SQL. A run holds at most a few hundred samples, and the arithmetic is far easier to test as ordinary code than as window functions.

func (*Store) UpdateTask

func (s *Store) UpdateTask(ctx context.Context, setID, taskID int64, patch TaskPatch) (*Task, error)

UpdateTask applies a patch to a task.

func (*Store) UpdateTaskSet

func (s *Store) UpdateTaskSet(ctx context.Context, name string, newName, description *string) (*TaskSet, error)

UpdateTaskSet renames a set and/or replaces its description. A nil field is left unchanged.

type Summary

type Summary struct {
	RunID     int64   `json:"run_id"`
	Status    string  `json:"status"`
	BaseAgent string  `json:"base_agent"`
	TaskSet   string  `json:"task_set"`
	K         int     `json:"k"`
	CostCap   float64 `json:"cost_cap"`
	CostSpent float64 `json:"cost_spent"`
	// BaselineVariant names the variant per-task deltas are measured against:
	// the first by creation order. This is a convention (the incumbent is
	// created first), not something the API enforces.
	BaselineVariant string           `json:"baseline_variant"`
	Variants        []VariantMetrics `json:"variants"`
	PerTask         []TaskMetrics    `json:"per_task"`
	Completeness    Completeness     `json:"completeness"`
	// Verdicts holds one decision per non-baseline variant, each with its gate
	// table, judge tally and per-category breakdown. Present even before any
	// judging: the objective half alone can already say "downgrade" or "no
	// regressions detected".
	Verdicts []VariantVerdict `json:"verdicts"`
}

Summary is the objective scorecard for one run — everything computable without a judge.

type SummaryOpts

type SummaryOpts struct {
	CompletenessFloor float64
	// WinThreshold is the judge win-rate a candidate must reach to be called an
	// upgrade.
	WinThreshold float64
	// GateRejectedPP is the largest tolerated rise in rejected tool-call rate,
	// in percentage points.
	GateRejectedPP float64
	// GateRoundsPct and GateCostPct are the largest tolerated relative rises in
	// mean rounds and cost per task.
	GateRoundsPct float64
	GateCostPct   float64
}

SummaryOpts carries the eval policy a summary is computed against. Thresholds are configuration, not constants: what counts as a regression is the operator's call.

type Task

type Task struct {
	ID       int64  `db:"id"       json:"id"`
	SetID    int64  `db:"set_id"   json:"set_id"`
	Prompt   string `db:"prompt"   json:"prompt"`
	Category string `db:"category" json:"category"`
	// PinnedHistory is a JSON array of {role, content} replayed verbatim as
	// the context preceding the turn. NULL/empty means a fresh turn.
	PinnedHistory        string    `db:"pinned_history"         json:"pinned_history,omitempty"`
	SourceConversationID string    `db:"source_conversation_id" json:"source_conversation_id,omitempty"`
	SourceMessageID      *int64    `db:"source_message_id"      json:"source_message_id,omitempty"`
	Tags                 string    `db:"tags"                   json:"tags"`
	Notes                string    `db:"notes"                  json:"notes"`
	CreatedAt            time.Time `db:"created_at"             json:"created_at"`
}

Task is one saved test case.

type TaskMetrics

type TaskMetrics struct {
	TaskID   int64                `json:"task_id"`
	Prompt   string               `json:"prompt"`
	Category string               `json:"category"`
	Variants []TaskVariantMetrics `json:"variants"`
}

TaskMetrics groups one task's per-variant cells.

type TaskPatch

type TaskPatch struct {
	Prompt        *string
	Category      *string
	PinnedHistory *string
	Tags          *string
	Notes         *string
}

TaskPatch carries the mutable task fields; a nil field is left unchanged.

type TaskSet

type TaskSet struct {
	ID          int64     `db:"id"          json:"id"`
	Name        string    `db:"name"        json:"name"`
	Description string    `db:"description" json:"description"`
	CreatedAt   time.Time `db:"created_at"  json:"created_at"`
	// TaskCount is populated by ListTaskSets; it is not a column.
	TaskCount int `db:"task_count" json:"task_count"`
}

TaskSet is a named collection of eval tasks.

type TaskVariantMetrics

type TaskVariantMetrics struct {
	VariantID   int64   `json:"variant_id"`
	Name        string  `json:"name"`
	SamplesOK   int     `json:"samples_ok"`
	MeanCost    float64 `json:"mean_cost"`
	MeanRounds  float64 `json:"mean_rounds"`
	MeanLatency float64 `json:"mean_latency_ms"`
	// Deltas are against the baseline variant and are zero on the baseline
	// row itself.
	DeltaCost    float64 `json:"delta_cost"`
	DeltaRounds  float64 `json:"delta_rounds"`
	DeltaLatency float64 `json:"delta_latency_ms"`
}

TaskVariantMetrics is one cell of the per-task breakdown.

type Variant

type Variant struct {
	ID      int64  `db:"id"      json:"id"`
	RunID   int64  `db:"run_id"  json:"run_id"`
	Name    string `db:"name"    json:"name"`
	Overlay string `db:"overlay" json:"overlay"`
}

Variant is one side of a comparison: a named overlay on the base agent's live config. An empty overlay is the incumbent.

type VariantMetrics

type VariantMetrics struct {
	VariantID int64   `json:"variant_id"`
	Name      string  `json:"name"`
	Overlay   Overlay `json:"overlay"`

	// RejectedRate and FailedRate are tool-call level: the denominator is
	// ok+rejected+failed+denied. Cached and suppressed calls are excluded
	// because nothing executed, so counting them would dilute both rates with
	// non-events.
	RejectedRate float64 `json:"rejected_rate"`
	FailedRate   float64 `json:"failed_rate"`
	// ToolCalls is that denominator, so a reader can tell a 0 % rate over 200
	// calls from a 0 % rate over none.
	ToolCalls int `json:"tool_calls"`

	MeanRounds float64 `json:"mean_rounds"`
	// WrapupCount is samples whose loop was cut short by repeated identical
	// calls or by exhausting the round budget — the "flaily" signal.
	WrapupCount     int     `json:"wrapup_count"`
	MeanCostPerTask float64 `json:"mean_cost_per_task"`
	MeanLatencyMs   float64 `json:"mean_latency_ms"`
	TotalCost       float64 `json:"total_cost"`

	SamplesOK     int `json:"samples_ok"`
	SamplesFailed int `json:"samples_failed"`
}

VariantMetrics is one variant's objective scorecard, computed over its status-ok samples.

type VariantVerdict

type VariantVerdict struct {
	VariantID int64  `json:"variant_id"`
	Variant   string `json:"variant"`
	Baseline  string `json:"baseline"`
	Verdict   string `json:"verdict"`
	// Reason is the one-line plain-language explanation, e.g. "downgrade: mean
	// rounds regressed +35% against a +20% threshold".
	Reason     string           `json:"reason"`
	Gates      []Gate           `json:"gates"`
	Judgment   Judgment         `json:"judgment"`
	Categories []CategoryResult `json:"categories"`
	// Divergence is set when the aggregate and a category disagree, e.g. "wins
	// overall; regresses on tool_heavy". v1 surfaces this prominently without
	// gating on it.
	Divergence string `json:"divergence,omitempty"`
}

VariantVerdict is the decision for one candidate variant against the run's baseline, with the work shown.

type Verdict

type Verdict struct {
	ID     int64  `db:"id"      json:"id"`
	ItemID int64  `db:"item_id" json:"item_id"`
	Winner string `db:"winner" json:"winner"`
	// Dimensions is a JSON object of dimension → winner (a/b/tie), the same
	// pairwise form as Winner rather than absolute scores: a judge comparing
	// two responses is reliable, a judge scoring one in isolation is not.
	Dimensions string `db:"dimensions" json:"dimensions"`
	Notes      string `db:"notes"      json:"notes"`
	// JudgeIdent names who judged — an API key name, or JudgeOperator for the
	// operator's calibration marks.
	JudgeIdent string    `db:"judge_ident" json:"judge_ident"`
	CreatedAt  time.Time `db:"created_at"  json:"created_at"`
}

Verdict is one judge's call on one item, expressed in presented letters.

Jump to

Keyboard shortcuts

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