EvalGlass v0.2.0
The first framework release since the 0.1.0 packaging cut: the epistemic core (M7) — Estimates
with confidence intervals, decision policies, capability-typed authority, digest-bound grants,
computed judge agreement — plus metric discovery (schema/prompt mining → a typed
MetricCatalog), live trace connectors (Langfuse / Phoenix / LangSmith, opt-in), an HTML
Scorecard, config-reachable trajectory/session units, diagnostic clusters, a continuous
drift watcher, and per-metric example selectors. Every new surface holds the no-false-confidence
line: new signal stays informational/proposed/uncalibrated until the host confers authority.
Added
- Continuous drift watcher (teta; ADR 0048) —
evalglass watch
makes drift detection continuous: it runs one evaluation, compares it to the configured
baseline, records a typeddrift.json, and exits (scheduled re-invocation via cron/CI, not a
resident daemon). It is the first production consumer of the paired-comparison core: aregression
is flagged only when the runs arecomparableand the paired interval clears zero (reusing
the interval-licensing rule); a delta inside the interval iswithin_noise;
not_comparable/missing_baselineis reported as exactly that, never "no regression". Drift is
explanatory evidence — a typed artifact + an INFO/WARNINGDiagnosticappended after the verdict —
so it adds no exit class (the exit still derives only from the run's verdict) and the watcher
never promotes the baseline. A "no comparable regression" result never implies quality is fine. - Diagnostic clusters in the Scorecard (teta; ADR 0047) — a
run's failing/non-scored items are now grouped by their sharedDiagnostic.codeinto a typed,
per-metric cluster view on theScorecard, turning a flat metric ("faithfulness = 0.82") into an
actionable failure mode ("the 18% that failed are all missing-citation cases"). Computed
effect-free from the scores by a pure, order-invariantcluster(); carried as an additive
Scorecard.clustersfield (pre-P3 scorecards byte-identical) that_verify_consistencyrecomputes
from the persisted scores, so a fabricated or hand-edited cluster fails closed.report.mdand
report.htmlrender cluster sub-rows (<code>: N cases) from the typed field only. A non-scored
item is grouped by cause, never coerced to0.0; a cluster adds explanatory structure only and
never changes a verdict or manufactures authority. This groups failure instances by cause — a
different axis from the explorer's call-identity grouping. connect --live <platform>verb (teta; ADR 0046) — the
shipped, opt-in Langfuse / Phoenix / LangSmith connector lanes are now reachable by a verb
instead of hand-writtenlanes:YAML.evalglass connect --live langfuse|phoenix|langsmithscaffolds/enables
the matching opt-in, experimental, deletableTRACE_SOURCElane inevalglass.yamlwith
credentials as env-var NAMES
(never literal secrets — a literal is rejected and never echoed), a fail-closeddata_policy
(defaults tounknown: egress refused until you setpermitted), andenabled: trueso the
followingrunpulls the traces. The verb imports no provider SDK and noadapters/trace_*
module; a missing extra/credential is a cleanSKIPPED; a live pull isproposeddata and
cannot gate. Scaffold-then-run; idempotent; host config preserved. Real but opt-in, off the
required path — never production live tracing, and never a proven pass.- Config-reachable trajectory / session units (teta;
ADR 0045) — atraces:route may now declare
unit: trajectory(orsession/step) to grade the whole agent run instead of one LLM
call at a time.run_configwires the existingselect_unitsselector and the
trajectory_shape@1aggregate built-in (now registered in the harness evaluator loader); an
aggregate's egress is the worst of its members (one forbidden member blocks it,
fail-closed). Absentunit:is byte-identical to before (defaults tocall). Aggregates run
over proposed trace data, so they stay informational and cannot gate; a trajectory with no
member output isnon_evaluable, never a fabricated0.0. Grade the trajectory; an aggregate
green is still not proof the agent succeeded. - Host command judge (teta; ADR 0042) —
adapters/judge_subprocess.py(SubprocessJudgeModel) runs a real host judge inside
evalglass runover a JSON contract (stdin{example_id, metric, input, output, reference, rubric}→ stdout{value|score, rationale}), the beta-proven subprocess pattern (like the M2
task runner;shell=False).judge: {adapter: command, command: [...]}selects it; thefake
adapter stays the default; egress is enforced upstream; every failure edge is non-OKevidence
with no value. The host judge lives inevals/judges/<name>.py. Uncalibrated → informational. - Dashboard HTML Scorecard report (teta; ADR 0043) —
harness/report_html.pyrenders a self-containedreport.htmlalongsidereport.md: verdict
hero, KPI tiles, per-workflow rows with the Wilson/Student-t interval band (the epistemic
honesty made visual), tier/authority chips, a "what this run does not claim" panel, and a
delta vs the previous run. Theme-aware; the page adds no authority. - Discovery scaffolder conforms to the canonical host layout (teta;
ADR 0041) —scaffold_catalognow
writes the canonicalevals/evalglass.yaml(never a parallel*.discovered.yaml), scaffolds
evaluators that import the vendored_evalglassruntime, and can wire an opt-in trace-
connector route (trace_connector=). A discovered suite drops straight into an installed host. - Metric Discovery capability (teta; ADR 0039) — the
discovering-metricsskill drives an agentic deep read of an app's traces, prompts, code, and
domain to derive domain-meaningful metrics anchored to operational failure modes, across a
quality taxonomy and three tiers (runtime / reference / judge), emitting a typedMetricCatalog
(src/evalglass/discovery/).scaffold_catalogturns that catalog into a runnable host eval
suite — catalog JSON +METRICS_CATALOG.md, aClaimSpecper metric, a rubric per judge metric,
scoped evaluator stubs, and a config — every assetproposed/informational/uncalibrated:
discovery drafts, the host decides. Validated end-to-end on a host traced with the opt-in
Langfuse connector. - Generic OpenAI-compatible judge lane (teta; ADR 0040)
—adapters/judge_openai.py(OpenAICompatibleJudgeModel), the tenth optional lane behind the
JudgeModelport: generic transport only (stdliburllib, https-only egress, NaN-rejecting
parse, no provider SDK), with per-metric rubrics injected by the host. Opt-in, deletable,
authority-free — an uncalibrated judge stays informational until a host runs an agreement study.
Complements the existing opt-in host-endpointlive-judgelane (ADR 0016). - Epistemic Core (teta milestone M7; ADR 0038) —
Wilson/Student-tEstimates,DecisionPolicy(lower-confidence-bound gating,min_n_effective,
max_missing_fraction), capability-typed authority (a fake judge can never gate), digest-bound
AuthorityGrant, computedJudgeAgreementStudy, content-addressedJudgeInstrument, optional
ClaimSpec, load-time recomputation + atomic persistence, and paired baseline deltas. All
additive (existing JSON still parses); one Verdict Engine. - Authoring tier + advanced extensions (v1.1; ADR 0025)
—/evalglass add-metric,add-judge,calibrateplus theauthoring-a-metric,
writing-a-host-evaluator,calibrating-a-judge, andpromoting-a-gateskills. Every scaffolded
asset staysproposed/uncalibrated/empty-authority; gate activation is a host YAML edit with no
verb.connect --liveis an opt-in, deletable optional lane (no provider SDK shipped);
connect --synthis governance-only (no generator); per-source-function views remain an unbuilt
advanced extension. Optional-lane absence never breaks the required local workflow. - Score subject identity +
/evalglass view --by-call(framework slice F1;
ADR 0024) —Scorenow carries optionalexample_id/
unit_id(additive provenance; no change to score meaning or the Verdict Engine), the engine
stamps it, and an artifact-shape gate proves realrunrecord.jsonoutput carries it.view
gains--by-call, grouping scores by explicit subject identity (never by list order).
Score→source-function mapping remains an advanced extension. - Codex second runtime (P3; ADR 0023) — packaged
from the same repo and the same canonicalskills/tree, no forked guidance:
.codex-plugin/plugin.json(sharedname/version/license+ a Codexinterface{}block),
AGENTS.mdas the Codex entry,.version-bump.json+scripts/bump-version.sh
(--check/--audit/--setacross every version surface), and a deterministic
scripts/sync-to-codex-plugin.sh(--stage/--dry-run/--fork). Runtime independence is proven
across runtimes (the vendored runtime is identical whichever runtime installed it). A public
Codex marketplace listing remains a maintainer step.
Changed
- The framework is project-agnostic. Scrubbed stray customer/domain-specific names from
framework source (three core docstrings, thediscovering-metricsskill, and the discovery
catalog) and genericized domain-specific examples. Once EvalGlass is installed in a host, all
customization (discovered metrics, rubrics, evaluators, the judge's domain framing) lives in that
host clone — never in the framework.
Fixed
- Vendored optional lanes were broken in a real install. The installer's namespace rewrite
only touchedimport/from evalglass…statements, so anExtensionLane's
module="evalglass.adapters…"string literal stayed un-vendored — a vendored host's lane
registry imported the absentevalglassnamespace, breaking every opt-in lane.vendor.py
now also rewrites pure quoted dotted module-path literals (prose/docstrings untouched). Surfaced
by running a config-driven eval through an opt-in trace-connector lane in a vendored host. - Trace connectors dropped the workflow name. The opt-in Phoenix and LangSmith connectors
did not carrytrace_name, so a host evaluator dispatching by workflow read every metric as
non-applicable; they now derive it from the provider's span/run name (as the opt-in Langfuse
connector already did).example_from_tracealso now propagates the envelope's metadata onto the
Example. - OpenInference JSON values are parsed.
_span_mapping.behavior_from_attributesparses a
JSON-mimeinput/outputvalue (a string) into the object it represents, so a connector no
longer hands an evaluator a string it reads as non-evaluable; already-structured or plain-text
values are left as-is. - Phoenix connector API drift. The
arize-phoenix-clientkwarg isproject_identifier(not
project_name), andlimit=Nonecrashed the SDK — the opt-in Phoenix lane now passes
limitonly when the lane declares one (found by a live run against the opt-in connector). - The installer's namespace rewrite corrupted a config filename. The module-path string
rewrite matched any quoted"evalglass.<name>"token, so theconnect --configdefault
"evalglass.yaml"became"_evalglass.yaml"in the vendored runtime only — the installed
connect --liveverb then wrote a stray, orphaned_evalglass.yamlinstead of the host's real
evals/evalglass.yaml(a silent misfire: exit 0, nothing wired). The rewrite is now restricted to
managed-package module paths (core/harness/adapters), leaving filename literals untouched.
Invisible to the source suite — only the vendored copy was wrong; surfaced by a field test of
connect --livein a vendored host. python -m …harness.cliemitted a runpy warning on every run. Theharnesspackage eagerly
importedcli, so runpy's double-importRuntimeWarningprinted on every documented invocation
(quickstart,run, CI).cliis now exposed lazily (PEP 562) — the warning is gone for all
entrypoints, with no change to any documented command.- Discovery dropped its own call-site classification. The scanner computed each site's
kind
(sdk/structured_output/model_construction/invoke) only to filter it away; the
serialized call site now carrieskindso a reviewer/scaffolder can act on it.
Full changelog: https://github.com/Syntelesis-Lab/evalglass/blob/v0.2.0/CHANGELOG.md