Module references
Module reference / Measure it
Crucible
- Lens
Exercise the production retrieval and answer path.
- Warrant
Gate consequential configuration changes on measured evidence.
- Lineage
Record compared configurations, scores, and release verdicts.
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.
Crucible evaluation runs and drift
Golden items, qrels, and a config feed a run; per-result rows roll up into the metrics endpoint and the paired comparison. Drift is a separate, deterministic sweep over documents and entities that raises deduped alerts.
Downloads
Concepts
- Modules
- Crucible
Keywords
- drift.kb · kb.stale · eval.regression
- graded relevance 0–3
- GET {id}/metrics
- assertions · citations · tags · concepts
- one execution, golden set pinned by hash
- one row per item × sample
- per assertion, human override wins
- tag = NULL is the overall row
- paired bootstrap + permutation
- documents · entities · mentions
- golden_items
- eval_configs
- eval_runs
- eval_results
- run_metrics
- drift_snapshots
- /evals/runs
- /evals/runs/compare
- qrels
- judgments
- id
- GET
- alerts
Source and generation provenance
Status: current
Generated at: 2026-08-17T18:32:18.102Z
Source hash: 8e5a1126633b4be53704abf4dc048996b0f7bb94f5c49bef1098c4dfbf13e94e
Metadata payload hash: 935a3a0fe634d34f89d4702ee83abc1b59c4177ce89048902eab24fc7d76863a
Canonical appearance
src/app/(docs)/docs/modules/crucible/page.tsx:52 route /docs/modules/crucible
All appearances
canonical—src/app/(docs)/docs/modules/crucible/page.tsx:52route/docs/modules/crucible
No mirrored appearances.
Generation versions
App: eli-ai 0.1.0
Mermaid: 11.16.0 · Mermaid CLI: 11.16.0
Node: v26.3.1 · Yarn: 4.17.1
Renderer config hash: 68c10966fe84406ee626034d58bfabd555df9f65f691204b7c46db24038da101
Renderer theme hash: c80287a78d80ad63d27bd5ca348b2ef9a7e2f44da289e436be6484ea28a1b033
Adapter versions: diagramGenerator=2, drawioFlowchart=1, drawioGantt=1, drawioSequence=1, drawioState=1
Full sidecar JSON: crucible-evaluation-runs-and-drift-8e5a1126.json
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.
Feature map
Five feature areas cover the module: what a workspace measures against, how runs are produced, how per-result rows become numbers, how two runs become a verdict, and how the corpus underneath is watched. Each area names the components the code map and invariants below locate.
System architecture
The write side and the read side are deliberately separate. Runs are produced by the runner — launched from the workspace app or the scheduler — while the three public operations only read what the runner persisted, plus one append-only judgment write. Nothing on the public surface can start or mutate a run.
Crucible — system architecture
Rectangles are API routes and services, the rounded node is the judge model, and cylinders are workspace tables under row-level security. The runner exercises the real answer pipeline and persists everything the read-side aggregation later serves.
Downloads
Concepts
- System architecture
- Public surface
- Scoring services
- Run production (workspace app + scheduler)
- Modules
- Crucible
Keywords
- GET {id}/metrics (API)
- GET (API)
- POST (API)
- Eval runner (service)
- model: judge
- store, append-only
- Scheduled runs + drift scans (service)
- Drift + freshness monitor (service)
- Run aggregation + paired significance (service)
- Real retrieval + answer pipeline under test (service)
- Metric math: retrieval, abstention, RAGAS (service)
- /api/v1/evals/runs
- /api/v1/qrels
- id
- /api/v1/evals/runs/compare
- store
Source and generation provenance
Status: current
Generated at: 2026-08-17T18:32:22.397Z
Source hash: 8b71cd979f407554e9aca770e4912be31e2342e0e6bebb7b062a9e1c3ec63ce1
Metadata payload hash: 051fc109f6e5d62cc8e3d14b2bba913ad54485dec8fa76e75d8ab635e1aeda89
Canonical appearance
src/app/(docs)/docs/modules/crucible/page.tsx:65 route /docs/modules/crucible
All appearances
canonical—src/app/(docs)/docs/modules/crucible/page.tsx:65route/docs/modules/crucible
No mirrored appearances.
Generation versions
App: eli-ai 0.1.0
Mermaid: 11.16.0 · Mermaid CLI: 11.16.0
Node: v26.3.1 · Yarn: 4.17.1
Renderer config hash: 68c10966fe84406ee626034d58bfabd555df9f65f691204b7c46db24038da101
Renderer theme hash: c80287a78d80ad63d27bd5ca348b2ef9a7e2f44da289e436be6484ea28a1b033
Adapter versions: diagramGenerator=2, drawioFlowchart=1, drawioGantt=1, drawioSequence=1, drawioState=1
Full sidecar JSON: crucible-system-architecture-8b71cd97.json
How to read the Crucible architecture
- Start with what a run consumes: The runner loads active golden items, graded-relevance rows, and a named config, then pins immutable config, target, and judge snapshots before the first item executes.
- Watch the scoring fan-out: Each item runs the real pipeline; the judge model grades assertions and truthfulness; the metric modules compute retrieval, RAGAS, and selective-prediction numbers per result.
- Separate reads from production: The metrics and compare endpoints call the aggregation helper over persisted rows; the qrels endpoint appends one judgment and its audit event.
- Follow the drift lane: The scheduled scan snapshots the corpus, evaluates thresholds from workspace settings, and raises one deduped alert per condition.
- Trust boundary
- Public bearer keys can read metrics, compare runs, and append qrels — run creation, golden authoring, and drift capture stay behind session-authenticated workspace surfaces.
- Durable state
- eval_runs pin goldenSetHash and snapshot ids; eval_results, judgments, run_metrics, qrels, drift_snapshots, and alerts are the persisted evidence every score resolves to.
Failure paths
- Provider outage marks results errored and the run degraded
- Fewer than two paired items yields an honest null verdict
- Duplicate qrel in the same lane no-ops to the existing row
- Drift thresholds breached raise a deduped drift.kb alert
Signals
- Run status mix (succeeded / degraded / failed)
- Per-tag run_metrics deltas across configs
- Abstention rate and mean truthfulness trends
- Open alerts by kind and severity
Where the logic lives
The code map locates each component. Table names stay in the notes; all are defined in the schema module with workspace row-level security.
Primary runtime flow
The highest-value public path is the read-and-judge loop over runs that already exist: aggregate one run, compare it against a baseline with paired significance, and append the human relevance judgments that sharpen the next run's retrieval metrics.
Crucible — primary runtime flow
The three public operations in sequence. Aggregation and pairing are pure reads inside short workspace transactions; the only public write is the append-only qrel with its audit event.
Downloads
Concepts
- Primary runtime flow
- Modules
- Crucible
Keywords
- GET {id}/metrics (API)
- GET (API)
- POST (API)
- eval_runs + eval_results + run_metrics (store)
- qrels + audit_log (store)
- append judgment + qrel.author audit event
- getEvalRunMetrics runId
- aggregate one run
- load run, per-result rows, overall run_metrics row
- Quality client (kb:read / kb:write)
- Eval aggregation helper (service)
- Paired-significance module (service)
- retrieval + RAGAS + abstention inputs
- compareEvalRuns a, b, metric
- pair per-item means by golden item id
- seeded bootstrap + sign-flip permutation
- delta, confidence interval, p-value
- two-gate significant verdict
- authorQrel queryId, chunkId, grade
- metrics summary with error classes and risk coverage
- /api/v1/evals/runs
- /api/v1/qrels
- /api/v1/evals/runs/compare
Source and generation provenance
Status: current
Generated at: 2026-08-17T18:32:20.358Z
Source hash: d2c7d473fa1b3035d9b50cbe4cc138849483b8945966f2aa2c6ced2a92e48c10
Metadata payload hash: 29bf6f6cdaac4b751275adb567c4c407b477129d87148d6e535c2f003ddcf73d
Canonical appearance
src/app/(docs)/docs/modules/crucible/page.tsx:113 route /docs/modules/crucible
All appearances
canonical—src/app/(docs)/docs/modules/crucible/page.tsx:113route/docs/modules/crucible
No mirrored appearances.
Generation versions
App: eli-ai 0.1.0
Mermaid: 11.16.0 · Mermaid CLI: 11.16.0
Node: v26.3.1 · Yarn: 4.17.1
Renderer config hash: 68c10966fe84406ee626034d58bfabd555df9f65f691204b7c46db24038da101
Renderer theme hash: c80287a78d80ad63d27bd5ca348b2ef9a7e2f44da289e436be6484ea28a1b033
Adapter versions: diagramGenerator=2, drawioFlowchart=1, drawioGantt=1, drawioSequence=1, drawioState=1
Full sidecar JSON: crucible-primary-runtime-flow-d2c7d473.json
How to read the measurement flow
- Aggregate one run: The helper loads the run's per-result rows and the overall run_metrics row, then computes abstention, risk coverage, and the error-class histogram on the fly.
- Pair before testing: Comparison keys per-item means by golden item id and tests only the intersection — items present in one run only are dropped.
- Read the two-gate verdict: significant is true only when the bootstrap interval excludes zero and the permutation p-value clears 0.05; both resamplers are seeded, so reruns reproduce exactly.
- Append judgments as you review: A qrel insert lands in its (source, judge-model) lane; a duplicate returns the existing row, and every new row writes a qrel.author audit event.
- Trust boundary
- A run belonging to another workspace is a 404 under row-level security; the public surface cannot launch runs or mutate results.
- Durable state
- Aggregation reads only persisted rows — eval_runs, eval_results, judgments, run_metrics — and qrels grow append-only with their audit trail.
Failure paths
- Unknown compare metric or missing run id returns 400/404
- pairedN below two returns null CI and p-value, significant false
- Metrics for a still-running run are served partial by design
- Qrel append into an existing lane returns 200, not a new row
Signals
- deltaMean with its confidence interval per comparison
- resultsCounted versus samplesN completeness
- errorClasses histogram shifts between configs
- Human qrel volume per golden item
Internals and invariants
Golden sets and graded relevance
Items are revisioned rows with atomic assertions rather than verbatim expected strings; citations anchor to document ids as required or forbidden; tags stratify metrics; concept anchors grade which entities a correct answer must touch. Qrels are plain-text-keyed on purpose so judgments survive item edits and chunk churn, and doc-level grades collapse at read time with source precedence human over provenance over llm, maximum grade within the winning source.
- Invariant — qrels are append-only: a duplicate in the same (source, judge-model) lane no-ops to the existing row, and every successful append writes a qrel.author audit event in the same transaction. Enforced in
src/app/api/v1/_lib/evalv2-metrics.ts. - Invariant— grade resolution applies the human > provenance > llm precedence at read time, never by mutating rows. Enforced by resolveDocGrades in
src/server/evals/golden.ts.
Evaluation runs
A run resolves its config, pins the golden-set hash plus immutable target and judge configuration snapshots, and only then executes items. Each item × sample runs the production pipeline, records an evidence snapshot, passes deterministic checks, and has every assertion graded by the judge model. Error classification assigns each result one deterministic taxonomy bucket.
- Invariant — what a run was graded against is frozen: goldenSetHash and the snapshot foreign keys are written before results exist, so later edits to items, configs, or models cannot rewrite history. Enforced in
src/server/evals/runner.ts. - Invariant — provider errors are recorded as errored results and excluded from metric means, and a run whose error fraction crosses the threshold is marked degraded rather than reporting a phantom regression. Enforced in
src/server/evals/runner.ts.
Metric math and aggregation
Per-result rows carry the metric payloads; the run level is always derived. The aggregation helper averages retrieval and RAGAS keys over the results that carry them, counts abstentions, sweeps the persisted confidence/correct pairs into the risk–coverage curve and AURC, and reads the overall run_metrics row for the quality block.
- Invariant — a metric mean only includes results that actually carried that metric; nothing contributes zeros, so partial instrumentation cannot dilute a score. Enforced in
src/app/api/v1/_lib/evalv2-metrics.ts.
Paired significance
Comparison pairs per-item means across the two runs, then runs a bias-corrected and accelerated bootstrap on the mean paired delta alongside a sign-flip permutation test. One seeded generator drives both resamplers.
- Invariant — identical run pairs and metric always produce identical intervals and p-values: the statistics are pure and seeded, with no unseeded randomness. Enforced in
src/server/evals/metrics/significance.ts.
Drift, freshness, and alerts
Snapshots fingerprint the corpus — entity counts per type, orphan rate, and document churn since the previous snapshot. The scheduled scan evaluates thresholds from workspace settings and raises one deduped alert per open condition; staleness compares the newest document and snapshot ages against the configured window.
- Invariant — alert rows commit in a short transaction and webhook fan-out runs only after the commit, so a delivery failure can never lose or duplicate the alert itself. Enforced in
src/server/evals/drift.ts.
For a self-hosting team the additive seams are the safe ones: contribute run metrics through the plugin surface (namespaced into the run_metrics plugin bag, so first-party numbers are never overwritten), author qrels from your own review tooling over the public endpoint, and tune drift thresholds per workspace. The pinning discipline — golden-set hash, config snapshots, seeded statistics — is what makes historical comparisons meaningful and is not designed to be relaxed.
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.