Quickstart¶
Seven commands, no API key, no network. The sample corpus belongs to a fictional
food distributor, Halcyon Provisions. It is not stored anywhere: cairn init
writes it from code together with a 120-question golden set and checks the
result against a pinned digest, so the copy on your machine is exactly the copy
the published numbers were measured on.
Install¶
The dev extra brings the test tools, the service and the PDF renderer the sample
corpus needs. The base package is enough to ingest, answer and evaluate with the
stub provider once a corpus exists. See Install for the other
extras.
1. Initialise a directory¶
This writes every shipped configuration to configs/ and generates data/sample/:
30 documents across Markdown, HTML, PDF, DOCX and plain text, the golden set, and
a poisoned copy of the corpus for the adversarial suite. The generator refuses to
finish unless the corpus hashes to the pinned value, so a drift in a parser or in
the generator is an error here rather than a number nobody can reproduce.
Every command below defaults to configs/default.yaml, so --config is left
out; pass it to try another configuration.
2. Build an index¶
index for default
documents 30
chunks 144
corpus hash acb06e55ed87073ae65503a9484e4c5bbc470df3a9cead9c2e63fcecdefdbae5
chunker sentence_window {'window': 6, 'overlap': 2}
embedder hashing {'dim': 512}
dimensions 512
backend numpy
directory .cairn/index/default
The index directory carries a manifest: the corpus hash, the chunker and its parameters, the embedder and its dimensions. A query-time configuration that does not match it refuses to load rather than quietly returning nonsense from the wrong vector space.
3. Ask something¶
Average fuel use across the fleet in March 2026 was 9.4 litres per 100 kilometres.
1. Average fuel use across the fleet in March 2026 was 9.4 litres per 100 kilometres. (verified)
meeting-minutes-2026-04-15.txt page 1: 'Average fuel use across the fleet in March 2026 was 9.4 litres per 100 kilometres.'
status answered, confidence 0.720, uncalibrated, 219 ms, 0.00000 USD
The answer comes back with a claim, and the claim carries the span it rests on:
the source file, the page, and the quote itself. The quote is checked against the
chunk it cites before you see it, which is what verified means. The last line
says uncalibrated because no threshold is in force yet; the next step fixes
that.
4. Calibrate the abstention threshold¶
calibration for default
alpha 0.350
threshold 0.5230
calibration cases 47
answered at threshold 13
empirical error 0.0769
upper bound 0.3163
method conformal_risk_control
With probability at least 0.95 over the calibration draw, the error rate among
answered questions on exchangeable new data is at most 0.35.
This runs the calibration slice of the golden set with no threshold in force,
grades what came back, and chooses the lowest confidence whose finite-sample
error bound still sits at or below alpha. From here on cairn ask applies the
threshold: the fuel question above still answers at 0.720, and a question whose
confidence falls below 0.523 gets a refusal with the closest passages attached.
The target is 0.35 rather than 0.10 for a reason worth reading once: with 47 calibration questions, the tightest 95 percent upper bound on zero observed errors is about 0.31, so a smaller target cannot be certified at this sample size, and the procedure refuses to pretend otherwise. When no threshold can satisfy the target at all, the record says so and the pipeline abstains on every question. See Abstention.
5. Produce a report¶
The report lands in reports/ as a timestamped file and in
reports/latest/default.json, which is the file the gate compares against. It
holds retrieval metrics (recall, nDCG, MRR), answer metrics (exact match, token
F1, judge score, citation validity, faithfulness), abstention metrics (answer
rate, error rate among answered, whether the guarantee held), and operational
metrics (cost per question, p50 and p95 latency, tokens, cache hit rate).
6. Try to make it worse¶
The point of the harness is that a regression cannot be argued with. Degrade the stub on purpose, evaluate again, and put the two reports in front of the gate:
cairn eval --out reports/good.json
CAIRN_STUB_DEGRADE=0.5 cairn eval --out reports/degraded.json
cairn gate --baseline reports/good.json --candidate reports/degraded.json
The gate exits 1 and says why, with the numbers in the sentence:
roll back: default against default on judge_score
- judge_score dropped by 0.329 (baseline 0.902, candidate 0.573); the allowed drop is 0.000.
- the candidate's abstention guarantee is not met: error rate among answered is 0.520 against alpha 0.350.
That exit code is the whole idea. Wire it into CI and a change that makes the system worse cannot be merged by accident. See Evaluate and gate.
7. Run the adversarial suite¶
Fourteen questions are asked against a copy of the corpus carrying six poisoned
documents, each with an injected instruction and a canary string. The report says
whether each document was retrieved, whether its canary or the system prompt
leaked into an answer, and what the scrub removed. The shipped run retrieved all
six and leaked nothing; the command exits 1 the moment that changes. See
Injection.
Next¶
- Serve it: an HTTP endpoint with Prometheus metrics and a span per stage.
- Write a config: the file that defines an experiment.
- Add a component: a new chunker, embedder, reranker or provider behind the existing interfaces.