TraceLens 0.5.0 makes evaluation results comparable and explainable. Every run records provenance (task content hashes, grader and adapter identity, runner settings) so two runs are checked for compatibility before they are compared; tracelens compare gives a verdict between two saved runs with a paired task bootstrap; tracelens inspect explains failed trials from a trials file; tracelens run --config tracelens.yaml replaces long flag lists; every command shares one exit-code contract; and the pass-rate, pass@k, and pass^k estimators were tightened so harness failures leave the denominator and unevaluable gates no longer pass. Releases are now prepared and published by the release pipeline.
Added
- One-click release preparation. The "Release prepare" workflow takes a
version, validates it against the tags and the changelog, moves the
[Unreleased]entries into a dated section (scripts/prepare_release.py,
which refuses to release nothing), and opens arelease: vX.Y.Zpull
request with the rendered notes; merging it makes the "Release tag"
workflow tag the merge commit and run the release workflow with
publish=true. Nothing releases on an ordinary merge, and the manual
tag path still works. (#88) tracelens report --format ci. Re-renders the one-line CI summary
tracelens runprinted, gate line included, from a saved results file,
so a job summary or script can read it without parsing Markdown.report
never re-decides the gate: it exits 0. (#75)- Releases create their GitHub Release automatically. The release
workflow now runs three jobs: build and verify (tag matches the built
version; release notes rendered from the changelog's dated section by
scripts/release_notes.py, failing before anything is published when the
section is missing or empty), publish to PyPI (skip-existingso a
re-run of an already-published tag is safe), and create the GitHub
Release with those notes and the wheel and sdist attached, marked as a
pre-release for any non-final version. The release step updates an
existing release instead of failing, so re-runs after a partial failure
are safe, andworkflow_dispatchis a dry run that publishes nothing.
docs/releasing.mdgains the verification checklist and failure/re-run
guidance. (#54) - Positioning and contributor guidance aligned with what is actually
demonstrated. The README and docs home lead with what TraceLens is
(repo-owned local regression checks, inspectable artifacts, no backend,
explicit uncertainty) and carry a "What is demonstrated today" table that
separates the tested mechanism from the not-yet-published downstream
evidence; the adjacent-tools page acknowledges that hosted platforms also
run evaluations, experiments, and CI checks and names no vendor features;
fingerprint explanations no longer claim exact reproducibility or causal
attribution (DecisionSpecdocstrings included);CONTRIBUTING.md
describes the current extras ([datasets],[docs], PyYAML in core),
themake verifygate, where things live, and three small first issues
(#74, #75, #76). (#53) - The documented user journey runs in CI from a built wheel.
tests/journey/test_user_journey.pydrives realtracelensprocesses
through the documented workflow in a scratch project (an existing
project,init,run --config, baselines from the README snippet, the
gate enabled intracelens.yaml, an intentional regression that blocks,
inspect,compare, a--task-idrerun, an infra outage and a grader
crash made unevaluable and told apart, malformed input and a bad config,
checkpoint/resume re-executing nothing,report,sample), checking
exit codes and persisted decisions at every step. A new CI job installs
a freshly built wheel into a clean environment and runs the journey
against its console script (TRACELENS_CLI). (#33, Stage A) tracelens inspect: explain failed trials from a trials file.
tracelens inspect eval/results/trials.json --failuresprints, per
failing trial, one kind (agent failure, infra error, or grader crash,
never conflated, harness causes first), status and attempts, expected
versus actual output (--eval-setjoins the task's name, input, and
declared expectation), every grader's verdict, score, metrics, and
feedback, and the transcript's steps, tokens, tool calls, and errors.
Absent fields readmissing; output is bounded (400 characters per
field, 20 steps per transcript) with an explicit count of what was
omitted, and--fulllifts the bounds. Filters:--kind agent|infra| grader|not-run|passed,--task-id,--grader(trials that grader
failed or crashed on),--all,--limit.--htmlwrites a
self-contained, escaped, offline drilldown that reads on a phone;
--jsonwrites the same view as data. The command exits 0 whenever the
file was read (it reports, the gate decides) and 2 on input errors.
tracelens run --task-id ID ...(run.task_idsintracelens.yaml)
reruns only the named tasks and refuses unknown ids; its provenance and
checkpoint identity cover the subset. New guide: "Debugging a Failed
Evaluation". (#52)tracelens compare: a verdict between two saved runs.
tracelens compare baseline-trials.json candidate-trials.json(and
compare_runs()in Python) implements the statistical contract's
run-versus-run section: tasks are aligned by content through the runs'
provenance (changed, added, or removed tasks and different graders make
the runs incompatible;--unmatched-tasks excludecompares the shared
tasks and lists the rest; artifacts without provenance align by id and
are labelled, or refused with--require-provenance), one statistic per
task and run is paired (--metric pass_rate | mean_score | <grader_id>.<metric_name>,--direction lowerfor latency-like metrics,
--graderfor multi-grader runs), and the mean paired difference gets a
percentile bootstrap over tasks, a sign-flip p-value (exact for small
suites), and a verdict against--threshold: improvement, equivalent
within the threshold, or significant but below it exit 0; regression
exits 1; inconclusive or insufficient evidence exits 2 (--observe
forces 0). The terminal summary ("what changed" from theDecisionSpec
diff next to "what moved" per task) and--output compare.jsonshare
every field, and the same inputs and--seedreproduce the record
exactly.examples/version_compare.pynow uses it. (#28)- Versioned run provenance and comparison compatibility. Every
EvaluationRunner.run()records aRunProvenanceon the batch
(batch.provenance;provenancein--outputand--save-trialsJSON;
a "Run Provenance" section in Markdown and HTML): ameasurementside
(eval-set and per-task SHA-256 content hashes, grader identities with an
optional declaredprovenance_version, runner settings) and a
candidateside (adapter identity,DecisionSpecfingerprint and spec).
check_compatibility(a, b)returns aCompatibilityReportthat is
compatible,incompatible(changed, added, or removed task content;
different graders), orunknown(no provenance on a side), with runner
and version differences as notes and candidate differences reported
separately with aDecisionSpecdiff. Baselines gaintask_hash
(TaskBaseline,update_baseline(task_hash=),promote(task_hash=);
results carrytask_summaries[].task_hash), and the CLI gate refuses to
compare a task whose content changed since its baseline was stored
(outcometask_content_changed, gate unevaluable, exit 2) instead of
matching on id; baselines without a hash still compare, with a warning.
Checkpoint identity now derives from the same hashing rule (values
unchanged). Artifacts written before this release load with
provenance=None; an unknownschema_versionis rejected clearly. The
tracelens initREADME snippet storestask_hashon each baseline. (#51) tracelens run --config tracelens.yaml. A project-owned run
configuration file holds exactly what therunflags hold (eval set,
adapter and graders, run counts, outputs, and the baseline gate).
Precedence is built-in defaults, then the file, then flags given
explicitly, so an omitted flag never resets a file value, and booleans
override in both directions (--progress/--no-progress,
--baseline-check/--no-baseline-check,--require-baselines/
--no-require-baselines). Paths in the file resolve relative to the
file; adapters and graders import fromrun.import_root(default: the
file's directory) so the command works from any directory; and the file
is parsed strictly with the safe YAML loader, so unknown keys, duplicate
keys, wrong types, unsafe constructs, and missing required settings exit
2 before any agent call.tracelens initnow writestracelens.yaml,
and the generated README and workflow run the same
tracelens run --config tracelens.yaml, so enabling the regression gate
is one edit to the config file. (#35)- Actionable CLI errors and discoverable outputs.
tracelens --debug(or
TRACELENS_DEBUG=1) adds the full traceback to input and configuration
errors, which are otherwise one or two lines on stderr with the next
action; an unimportable adapter or grader now explains the dotted-path
and project-root requirement.tracelens runvalidates--num-runs,
--max-concurrency,--timeout, and--max-infra-retriesbefore doing
anything, and lists every artifact it wrote on stderr
([tracelens] wrote results: ...) while stdout carries only the summary.
(#48) tracelens runaccepts JSONL and CSV eval sets.--eval-setpicks
the loader from the file suffix (.json,.jsonl,.csv); a directory
needs--eval-set-format json|jsonl|csv.--input-fieldand
--metadata-fieldsmap foreign JSONL/CSV columns the same way the Python
loaders do. The dispatch is also available as
tracelens.loaders.load_tasks(), which raisesEvalSetLoadErrorwith the
CLI's message. Hugging Face Hub datasets stay a Python-API concern. (#50)
Changed
examples/hello_world.pytakes--reports-dir. The default is still
examples/reports/(the checked-in sample the README links to); the test
suite now writes to a temporary directory instead of rewriting the sample
on every run. (#74)- PyYAML is a core dependency (
pyyaml>=6.0), used only through the
safe loader for--config. Flag-onlytracelens runinvocations are
unchanged, except that--eval-set,--adapter, and--gradersare now
required only when no config file provides them; a run missing any of
them still exits 2, naming both the flag and the config key. (#35) - The scaffold and the user guide show
provenance_version. The
adapter and grader examples in the user guide declare it with a note on
when to bump it, andtracelens initwrites a commented
# provenance_version = "starter-1"line intoeval/adapter.pyand
eval/grader.py. (#76) - One exit-code contract for every command. 0 = success or gate passed;
1 = a negative result (blocked gate, unmet--require-baselines,
calibration below threshold); 2 = a usage, configuration, or input error,
or an unevaluable gate. Consequently an unimportable adapter or grader,
a missing / invalid / non-trials input totracelens sample, a missing or
invalid annotations, results, transcripts, or samples file for
tracelens calibrate(and a worksheet with no usable rows or
--transcriptswithout--grader/--samples), andtracelens init
refusing to overwrite without--forceall exit 2 instead of 1 (or a
traceback). Negative results are unchanged. (#48) - Eval-set load failures exit 2. A missing path, unsupported suffix,
directory without--eval-set-format, invalid JSON, malformed record, or
missing input column now prints a concise error naming the file (and the
line when the loader knows it) and exits 2 before any agent call;
previously these exited 1 or raised. (#50) - Report JSON records the baseline gate decision.
tracelens run --outputnow writes agateobject (statusnot_requested/passed/
blocked/unevaluable, exit code, threshold, noise band, task counts,
reasons, and per-task outcomes with the observed regressions), and
ReportData.gatecarries it in Python. Files written by earlier versions
have nogatekey and load withgate = None; no decision is invented. tracelens reportand output writing fail clearly.reportexits 2
with a message for a missing results file, invalid JSON, or a document
that is not a TraceLens results file (ReportData.from_dictnow raises
ValueErrorinstead of rendering an empty report), andtracelens run
exits 2 when an--output/--report/--html-report/
--save-trialspath cannot be written. Previously both produced
tracebacks.- Gate comparisons use gradable trials only. The per-trial samples fed
to regression detection now followTrial.is_gradable, soPENDING,
RUNNING, andSKIPPEDtrials are excluded like harness failures
(previously they counted as failures);INFRA_ERRORand grader-crash
exclusion is unchanged. - Harness failures and never-run trials leave the pass-rate denominator.
TrialBatch.pass_rate,passed_count,get_pass_results_by_task(), and
get_pass_sequences_by_task()now consider only gradable trials
(Trial.is_gradable:COMPLETED,FAILED, andTIMEOUTwithout a grader
crash).INFRA_ERRORtrials, grader crashes, andPENDING/RUNNING/
SKIPPEDtrials are excluded and appear asNonegaps in run sequences;
the newgradable_count/excluded_countproperties and the report's
gradable_trialsfield carry the denominator. Suite mean score likewise
averages gradable trials only. The CLI baseline gate already excluded
these trials, so gate decisions are unchanged; overall pass rates in
reports rise for runs that had harness failures. Migration: batches that
reportedpass_rate = passed / total_countnow report
passed / gradable_count; usebatch.total_count - batch.gradable_count
(orReportData.excluded_trials) to see what was excluded. - No more
c / nfallback for pass@k belowkruns.
pass_at_k_estimator,PassAtKAnalyzer.analyze(), and
compute_confidence_interval()treat a task with fewer thankgradable
runs as ineligible, matchingpass_to_k_estimator. The float APIs return
0.0when no task is eligible (their documented placeholder); use the new
availability APIs below to tell that apart from a measured zero. Migration:
code that relied onpass_at_k_estimator({"t": [True]}, k=5) == 1.0
should either runktrials per task or read
pass_at_k_metric(...).value is None. - Report JSON carries availability.
ReportData.pass_at_kand
reliabilityvalues (and their per-task counterparts) may benullfor
an unavailable metric, and new top-level keysmetric_availability,
availability_recorded, andgradable_trials(plusgradable_trials
per task summary) are written. Reports written by earlier versions still
load: they getavailability_recorded = false, their values are shown as
recorded, andgradable_trialsfalls back tototal_trials.
Fixed
tracelens initgenerates a CI workflow that evaluates agent changes
and installs reproducibly. The generated.github/workflows/eval.yml
used to trigger only oneval/**,pyproject.toml, anduv.lock, so a
pull request that changed only agent code skipped the eval; it installed
withuv pip install tracelensafteruv sync, whichuv runthen
removed again; it used older action refs than this repository's own CI;
and its summary step failed oncatwhen a preflight error meant no
report was written. It now runs on every pull request tomain(a
paths:filter is shown as an explicit customization), uses
actions/checkout@v6and the same pinnedastral-sh/setup-uvas this
repository, installs an existing project fromuv.lockwith
uv sync --frozen(or creates an environment in a bare repository) and
installs TraceLens only when the project does not already provide it,
pinned to the release that generated the file, runs
.venv/bin/tracelensdirectly, tolerates missing report files in the
summary and artifact steps, and carries the three gate flags as a
commented block. The generatedeval/README.mdis a four-step
walkthrough: run, what CI does, make it yours, and enable the gate with a
baseline-storing snippet plus a "prove it blocks" step; an end-to-end test
executes that walkthrough (init, run, store baselines from the README's
own snippet, break the agent, confirm exit 1 andBLOCKED).
examples/ci/eval.ymland the CI/CD guide are aligned with the template.
(#49)- The gate decision is made once and shown everywhere.
tracelens run --baseline-checkused to write JSON/Markdown/HTML before comparing against
baselines, so a run that exited 1 on a severe regression saved artifacts
that said nothing about it, and re-rendering withtracelens reportlost
regression data entirely. The comparison now runs first
(tracelens.reporting.gate.evaluate_gate), the resultingGateResultis
attached to the report before any file is written, and the exit code, the
stdout summary, the JSONgateobject, and the Markdown/HTML "Baseline
Gate" sections (status, policy, task counts, reasons, a regression table
with baseline/current/change/severity/notes, skipped tasks) all come from
it. All-infra and all-grader-error runs stay distinguishable from agent
regressions in every format, and grader-error rate and token totals are
now rendered in Markdown, the CI summary, and HTML. (#47) - Unavailable metrics render as N/A, never as zeros. A one-run-per-task
suite used to reportpass@5 = 1.0(a fallback) andpass^5 = 0.0(no
eligible task) as if measured. NewMetricValue
(tracelens.statistics),pass_at_k_metric,pass_to_k_metric, and
PassAtKAnalyzer/ConsistencyAnalyzer.analyze_detailed()return the
value with its evidence: eligible/total task counts, the runs the metric
needs, the most runs any task recorded, and a reason when unavailable.
Markdown showsN/A: needs at least 5 gradable runs per task; 0/2 tasks eligible; max 1 gradable run(s) recordedplus a--num-runshint, the
CI summary printspass@5=n/a, HTML lists unavailable metrics under the
chart instead of drawing zero-height bars, and available values carry
their eligible/total counts. Pass rates with no gradable trial render as
N/Ain every format, and per-task rows showtrials (gradable)when
they differ. Legacy reports get an explicit note that availability was
not recorded. (#46) - pass^k no longer depends on trial completion order. The runner appends
trials as they finish, andTrialBatch.get_pass_results_by_task()returned
them in that order, so the consecutive-window pass^k changed with
concurrency timing and checkpoint resumes: run-index outcomes
[T, T, F, F]reported pass^2 = 1/3 when trials finished in order and 0
when they finished in order 0, 2, 3, 1. Results are now ordered by
run_index. NewTrialBatch.get_pass_sequences_by_task()returns
run-indexed sequences withNonefor missing runs;pass_to_k,
pass_to_k_estimator, andConsistencyAnalyzeraccept them, a window
that would span a gap is not counted, and a task with no complete window
is ineligible at thatk. Reports use these sequences for pass^k. Two
trials sharing a(task_id, run_index)now raiseValueErrorinstead of
producing an ambiguous sequence, andpass_to_krejectsk < 1. The
consistency.pydocstring examples that claimed
pass_to_k([T, T, F, T, T], 3) == 0.333are corrected to0.0. pass@k
is order-invariant and unaffected. (#45) - pass@k bootstrap intervals preserve repeated task draws.
PassAtKAnalyzer.compute_confidence_intervalandanalyze_with_ci
resampled task IDs with replacement but collected them into a dict keyed
by task ID, so a task drawn twice counted once: the draw[A, A, B]with
A=1 and B=0 averaged to 1/2 instead of 2/3. Each resample lost about 37 %
of its draws and reported intervals were roughly 20–25 % too narrow at
typical suite sizes; intervals from earlier releases were overconfident
and should be recomputed. Both methods now compute per-task pass@k once
(in sortedtask_idorder) and delegate tobootstrap_ci, and gain an
optionalseedargument: the same inputs and seed give the same interval,
and input task order no longer affects it.bootstrap_ci, and therefore
the analyzer, now raisesValueErrorforconfidenceoutside(0, 1)or
n_bootstrap < 1instead of failing inside NumPy. Point estimates and the
(lower, upper)return shape are unchanged. The estimator, sampling-unit,
and trial-validity definitions every statistic follows are now written
down indocs/statistical-contract.md. (#44) - Unevaluable baseline gates no longer pass.
tracelens run --baseline-checkexits 2 when any baseline-backed task has no gradable
trials or no comparable CLI metrics, or when no task can be checked at all
(including empty suites, zero runs, and empty/unrelated baseline files).
The summary marks the gateUNEVALUABLEand retains observed regressions
and exclusion counts. This intentionally changes the previous exit-0
behavior: infrastructure and grader failures remain excluded from agent
regression samples, but missing evidence cannot authorize a passing gate.
An unevaluable gate takes precedence over exit 1 for policy violations;
otherwise regression and required-baseline failures still exit 1. Partial
trial loss remains allowed when every baseline-backed task retains a
gradable sample. Non-gated runs are unchanged.
Install: pip install tracelens==0.5.0 · PyPI · Full changelog