Module references
Crucible
Crucible is the measurement system: golden sets of questions with atomic assertions, graded relevance judgments over retrieved evidence, evaluation runs scored against both, and drift snapshots that notice when the corpus underneath moves. It turns "this feels better" into a number, and then into a signed delta with a confidence interval and a p-value, so a configuration change can be accepted or rejected on evidence. Its public API surface is deliberately small and read-mostly: read one run's aggregated metrics, compare two runs with paired significance, and append a relevance judgment.
Use it standalone
Adopting Crucible alone means one package subpath and one API key. Nothing here imports another capability module, and none of the three endpoints requires a document, graph, connector, or agent to be configured — they read rows that already exist.
Mint a key with the right scope
Reads (getEvalRunMetrics,compareEvalRuns) requirekb:read; the routes also accept a legacyruns:readkey. Authoring a qrel requireskb:write. For a delegated user key (eli_uk_) the owner must additionally still hold theevals:readcapability for the reads andevals:writefor the write — the route passes those to the guard, and a revoked capability returns 403insufficient_capability.Install one subpath
@eli-ai/client/qualityfor headless use, or@eli-ai/react/qualityfor the components. The React subpath pulls in@eli-ai/react/providerand@eli-ai/react/hooksfrom the same package and nothing else.Call it
Every method takes an optional per-callworkspaceIdandAbortSignal. Responses are plain JSON contract types — no envelope to unwrap.
What Crucible alone does not give you over HTTP
/api/v1 surface has no operation that creates a golden item, launches an eval run, diffs two runs by assertion flip, captures a drift snapshot, or lists alerts. Those live on the session-authenticated workspace BFF under /api/w/{workspaceId}/evals/*, /api/w/{workspaceId}/golden/*, and /api/w/{workspaceId}/evals/drift, which are guarded by workspace membership and capabilities (for example evals:execute) rather than a bearer key. A standalone adopter reads metrics, compares runs, and appends qrels over the API; runs are produced in the eli.ai workspace or by the scheduled runner.React components
Four components are exported from @eli-ai/react/quality, together with the EliQualityLabels, EliQualityProps, and EliPolicyDecisionsProps types. They are client components (the file carries "use client") and each builds only a quality client from the provider's transport. Markup uses scoped eli-* classes; import @eli-ai/react/styles.css for the shipped styling, or leave it out and write your own.
EliProvider takes a transport (or an aggregate client, whose transport is retained) plus an optional workspaceId that the components pass as a per-call override. It has no API-key prop by design.
Keys do not belong in the browser
eli_sk_ key. Inject a fetch that calls a same-origin proxy which adds the credential server-side — see the SDK guide for the full reasoning.EliEvalMetrics
The focused Crucible surface. Given a run id it fetches GET /api/v1/evals/runs/{id}/metrics once and renders: a status badge (succeeded positive, failed critical, anything else warning), a resultsCounted/samplesN samples counted line, a KPI grid with a meter per value, abstention badges, and — when the run stored selective-prediction pairs — an inline SVG risk–coverage curve with one point per answered result.
The KPI grid shows exactly seven values, each rendered as a rounded percentage or — when null: accuracy, groundedness, citation precision, citation recall and retrieval hit rate from quality, plus recallAtK and ndcgAt10 from retrieval. The API also returns precisionAtK, mrr, hitAtK, the RAGAS block and the error-class histogram — this component does not draw those, so read them from the endpoint or a hook if you need them.
EliEvalMetrics props
EliQuality
The composite dashboard. It renders three sections in one surface: an answer-policy table (verdict, question, surface, confidence as a rounded percentage) with a total-decisions badge, an evaluation section whose form takes a run id and loads that run's metrics on submit, and a semantic-cache KPI grid. Policy decisions, policy stats and cache stats load on mount; the evaluation panel is idle until you submit, and the submit button is disabled while the input is blank or a request is in flight.
Two of those three sections do not read Crucible endpoints — see How it composes below. A single kb:read key covers all three.
EliQuality props
EliPolicyDecisions
A standalone table of answer-policy decisions — verdict badge, question, surface, and a formatted createdAt — with optional surface and verdict filters. It reads the append-only decision log, not evals; it is exported from this subpath because it shares the quality client. Verdict badges are tone-mapped: answer and proceed positive, abstain and abstain_with_pointers critical, everything else warning.
EliPolicyDecisions props
EliCacheStats
A four-tile KPI grid for semantic-cache health: hit rate (with a meter, or — when null), active entries with the embedding-backed count beneath, invalidated entries with the expired count beneath, and the last hit timestamp with the current policy mode (off · shadow · enforce) beneath. It reads GET /api/v1/cache/stats, which is the answer path's cache, not an eval surface.
EliCacheStats props
Prefer your own markup?
createQualityClient(transport) inside useEliQuery / useEliMutation from @eli-ai/react/hooks. That is exactly what these components do internally, so the fetch, abort and error semantics are identical.HTTP API
Three operations. All are workspace-scoped through the bearer key plus an optional X-Workspace-Id header, and every failure is the shared { error, message } envelope — body-validation failures add an issues array. A run that belongs to another workspace is a 404, not a 403.
/api/v1/evals/runs/{id}/metricskb:read (runs:read accepted) · evals:readAggregates one run's per-result rows to the run level. The path parameter `id` is the eval run id. `retrieval` is null unless at least one result carried a retrieval metric; `ragas` is null unless a result carried a RAGAS metric; `quality` is null when the run has no overall run_metrics row. Individual metric fields are null when nothing contributed to that mean. 404 when no run with that id exists in this workspace.
Field names above are the response contract; the numbers are illustrative. Three details are worth internalising before you build on it:
statusis the run's own status —running,succeeded,failed, ordegraded. Metrics are served for a run that is still running; they are simply partial.abstention.meanTruthfulnessis on the CRAG signed scale (perfect 1, acceptable 0.5, missing/abstained 0, incorrect −1), so it is not a percentage and can be negative.aurcis the mean risk over the swept operating points — lower is better — and is null when the run stored no selective-prediction pairs, in which caseriskCoverageis an empty array.errorClassesis a histogram keyed by the deterministic error taxonomy the runner assigns per result:retrieval_miss,hallucination,citation_error,stale,incomplete,conflict_mishandled,correct. Results with no error class contribute nothing.
/api/v1/evals/runs/comparekb:read (runs:read accepted) · evals:readPairs run B against run A by golden item id (per-item mean across that item's samples) and reports the mean delta b−a with a 95% percentile-bootstrap CI and a two-sided sign-flip permutation p-value. `significant` is a two-gate verdict: the CI excludes 0 AND p < 0.05. Both resamplers are seeded, so the same two runs always produce the same numbers. 400 when a or b is missing or metric is unknown; 404 when either run is absent from this workspace.
Query parameters
pairedN is the number that matters
a.n and b.n count the items each run scored on that metric; pairedN counts the intersection, and only the intersection is tested. Items present in one run but not the other are silently dropped. With fewer than two pairs, ci and pValue are null and significant is false — an honest "not enough evidence", not a failure./api/v1/qrelskb:write · evals:writeAppends a TREC-style graded-relevance judgment for a (golden item, chunk) pair. The table is append-only: a human override inserts a new row rather than mutating a machine judgment, and the metrics reader resolves precedence at read time. A duplicate within the same (source, judgeModel) lane is a no-op that returns the existing row with 200; a newly appended row returns 201.
Request body
Grades collapse per document at read time with the precedence human > provenance > llm; within the winning source the maximum grade wins. A successful append also writes a qrel.author row to the audit log, targeting the queryId.
Data elements
Every table below carries workspace_id and a *_tenant_isolation row-level-security policy asserting workspace_id = current workspace for both USING and WITH CHECK, so every read and write in the module runs inside a workspace transaction under FORCE RLS. There is no cross-workspace read path.
Two schema decisions shape what a consumer can rely on. Qrel keys are plain text, not foreign keys — queryId and chunkId carry no referential constraint, so judgments survive golden-item edits and chunk churn from re-ingestion. And a run pins what it was graded against: goldenSetHash plus the targetSnapshotId / judgeSnapshotId foreign keys into immutable configuration snapshots mean a historical comparison stays meaningful after the set and the models have both moved on.
How it composes
Crucible is usable on its own for reading and judging, but it measures something — and that something is produced by other modules. Here is the honest split.
- Lens — needed to produce runs. An eval run routes each golden item through the real retrieve-and-answer pipeline; that is where
retrievalMetrics,abstainedandtruthfulnesscome from. Without an answering pipeline there is nothing to score. Reading metrics and comparing runs still work perfectly on runs that already exist. - Warrant — powers two of
EliQuality's three sections.EliQualityandEliPolicyDecisionsreadGET /api/v1/policy/decisionsandGET /api/v1/policy/stats— the append-only answer-policy decision log written by the chat, structured and agent pipelines. Without the policy engine those panels render their empty state; the evaluation section is unaffected. Samekb:readscope, different system. - Atlas — unlocks concept-anchored scoring.
golden_concepts.entityIdandgolden_assertions.conceptIdreference graph entities, and the drift snapshot's entity counts and orphan rate are computed from entities and their mentions. With no graph, leaveconceptIdnull — assertions stay plain text and everything else still scores. - Intake — gives citations and drift something to point at. Golden citations anchor to document ids, and
source: "provenance"qrels are derived from an item's required citations (graded 3, keyed at document granularity). Drift'sdocChurnanddocCountare counts of ingested documents. Human and LLM qrels need none of this. - Ports — how the three operations are published.
getEvalRunMetrics,compareEvalRunsandauthorQrelare catalogued under thequalitycapability, withkb:read/kb:writeas the canonical scopes andruns:readretained as an accepted read scope on the two GETs. - Lineage — complementary, not required. Crucible writes its own audit event for qrel authoring and keeps its own append-only trails (qrels,
eval_feedback, judgment overrides). Nothing in the module reads a lineage surface.
Conduit has no direct relationship with this module: nothing in Crucible's code reads a data connector or a live query. If an evaluated answer happens to include live data, that arrives through the answer pipeline, and Crucible sees only the resulting text and citations.
For the conceptual model behind the scores, see the evaluation framework and Evals & drift; for authoring golden items and launching runs in the workspace UI, see Golden sets & eval runs and Retrieval metrics.
Extending Crucible
evalMetrics. The host namespaces the key to plugin.<id>.<metric> and writes it into the additive run_metrics.plugin_metrics jsonb bag, so a contributed number can never collide with or overwrite a first-party metric, and a regression comparison can always tell the two apart. Crucible emits eval.run.completed, drift.detected and alert.raised. See the extension model for the contract and the trust boundary, and Build a plugin for a worked example.