compression with a quality contract

Evaluation

Every compressor asks you to trust it. This page is what distil measures instead — including the numbers that make distil look worse, because a suite that only ever produces good news is a suite nobody should believe.

Why compression ratio is not a result

Token reduction and task success are not linearly related, and the relationship can cliff-edge. One study found 4× compression collapsing SWE-bench resolution from 86% to 7% — not a decline, a cliff.

We hit it ourselves. E7 ran the full SWE-bench Verified agent loop under distil's aggressive tier and measured real resolution: 52% → 16%. The reversible tier held (56% vs 52% full-context). Per-step decision-equivalence had already passed. End-to-end task success still cratered.

We publish E7 as evidence, not confession. It is why the shipped default is the conservative tier rather than the one that wins a compression-ratio leaderboard.

The rule that follows: every compression claim reports token savings and a task-success delta together, at the setting we actually ship. A ratio with no success number beside it is an invitation to find the cliff in production.

Distil's evaluation stack: five per-commit gates — bench for decision equivalence, verify for byte fidelity, retention for fact recall, fidelity for state fidelity, validate for adversarial invariants — under a conformal risk certificate, with external-validity checks and a versioned record for every run.

Five layers, five different questions

LayerQuestionCommandCost
Decision equivalenceDoes the agent's next action change?distil benchfree
Byte fidelityIs every compression exactly reversible?distil verifyfree
Fact recallWhich facts stay visible, recoverable, lost?distil retentionfree
State fidelityDoes a yes on recall actually mean anything?distil fidelityfree
AdversarialDo the invariants hold on hostile input?distil validatefree

All five are offline, need no API key, and run in seconds. That is deliberate: a gate you skip because it costs money is not a gate. All five run per-commit in CI.

The case recall cannot see

A file created at turn 2 and deleted at turn 4. When compression drops turn 4 every path token remains, so string recall reads 100 percent, but artifact-state fidelity reads 0 percent — the failure is stale (present but wrong) rather than lost.

Recall asks "is the fact still there". Here is a trajectory where the answer is yes and the agent is still broken:

turn 2   Write(file_path="net/scratch_bench.py")
turn 4   rm net/scratch_bench.py

Compress away turn 4. Every path token is still present — string recall reads 100% — while the agent now believes a file exists that does not, and will plan around it. Nothing in the transcript says otherwise.

distil fidelity folds tool calls into a file-state ledger and grades the final state, splitting two outcomes that presence metrics conflate:

OutcomeMeaningSeverity
exactfinal state preserved
lostpath absentloud — the agent can see the gap
stalepath present, wrong statesilent — the agent acts on a false belief

These are never averaged into one accuracy figure, because a compressor that drops a whole file history is safer than one that preserves half of it. On the case above: string recall 100%, state fidelity 0%.

Factory.ai measured every method they tested at 2.19–2.45 / 5.0 on this axis across 36,611 production engineering messages. Presence-based metrics are structurally unable to detect it — the string is present.

Three more things recall misses

Overclaim. "approximately 4200 ms""4200 ms". Byte-identical value, and every recall metric scores it perfect — but the agent has been handed a precision the source never asserted. The hedge was the information. Hedges are grouped into classes so reshaping (approximatelyabout) is not penalised; only the disappearance of hedging is. Direction is asymmetric: overclaim is gated, underclaim only reported.

Continuation. Whether the agent still knows what is left to do. Dropping a completed item is cheap — work gets redone. Dropping a pending one is silent: the work is skipped and success is reported anyway.

Propagation. Whether a loss at turn k shows up as a behaviour change at turn k+n, as a lag-lift profile. Two limits are printed in the tool's own output: it is association rather than causation, and periodic workloads alias at multiples of their period.

The numbers, on the shipped tier

$ distil fidelity
artifact-state fidelity   100.0%  (7/7 artifacts intact)
  stale (wrong state)       0.0%  (0)
  lost  (absent)            0.0%  (0)

hedge fidelity             94.7%  (162/171 claims kept their hedging)
  overclaimed               5.7%  (9)  <- value kept, uncertainty dropped

pending-work recall       100.0%  (2/2 remaining items still visible)

error propagation  (36 turns, base decision-change rate 0.0%)
  verdict: no decisions changed at all — nothing to propagate, and nothing tested

OUTPUT surface — past answers digested on re-entry (the other half of the bill)
  (6 blocks changed by digestion, 4 facts removed from them — the graded evidence)
  artifact state         100.0%  (7/7)   stale 0
  hedge fidelity         100.0%  (6/6)   overclaimed 0
  pending-work recall    100.0%  (2/2)   dropped 0

silent failures, BOTH surfaces: 9  (input 9, output 0)

Those 9 overclaims are real, and we ship the number rather than tune it away. Tier-1 digests spans behind restore handles, and a digested hedged sentence can keep its value while losing its qualifier. Unlike a lost fact this is not recoverable in practice: a missing fact prompts the agent to expand, a missing hedge gives it no reason to look.

So the CI gate is --max-silent 15, not 0. Gating at zero would assert a property the compressor does not have. The band fails a regression that doubles it — and fails an improvement too, forcing the number to be re-read rather than drifting.

Public benchmarks — falsifiable by a stranger

Every other number on this page is graded against distil's own corpus. Rigorous, and unfalsifiable by someone who doesn't trust us. distil suite closes that: twelve public benchmarks whose answer keys were written by other people.

It costs nothing to run. Grading is deterministic recall against the answer key — no model in the loop, no API key, no spend. Suites that grade with an LLM judge cost real money per tier, which makes them something you run before a launch rather than before a merge. This one is wired into make gate and the CI gate job.

Two benchmark cases compared: a thin-payload GSM8K question with nothing to compress, labelled a control, beside a rich-payload BFCL tool schema whose compression result is evidence.

The payload column is the important one. A GSM8K case is a one-line word problem: there is nothing in it to compress, so an unchanged score proves the compressor left it alone. That is a control. Only rich-payload rows — tool schemas, retrieved passages, long-form narrative, code — can demonstrate compression quality. A run that grades only controls exits 1.

bfcl leads tier 1 deliberately. Berkeley Function Calling compresses the tool schema and checks that every name the gold call is built from — the function and each argument it passes — survives compression: the failure an agent proxy is most likely to cause and least likely to notice, because no QA benchmark ever asks the model to act. The gold call itself is not graded as an answer — it never appears in the schema text, and checking whether a model still emits it would need a model in the loop, which would stop this suite running in the CI gate. What is measured is stated exactly.

uv run distil suite --tier 1              # tool-calling + retrieval
uv run distil suite --tier 1 --tier 2     # add the harder payloads
uv run distil suite --only bfcl -n 50     # one benchmark
uv run distil suite --json --max-lost 0   # gate

A benchmark that cannot be fetched is reported as a FAILED row and exits 1 — never dropped. A suite that silently skips what it could not load reports a clean sheet for a run that measured less than it claimed.

Every run is a record, not a number

--json emits an envelope, validated against a published JSON Schema on every test run:

{
  "schema":  "distil.eval/1",
  "subject": { "compressor": "serving", "module": "distil.compress.strategies" },
  "dataset": { "trajectories": 9, "fingerprint": "sha256:051b836358932883" },
  "grader":  { "kind": "deterministic",
               "detail": "synthetic DECISION: oracle — NOT a model" },
  "metrics": { ... },
  "gates":   [ { "name": "max_silent", "threshold": 15, "observed": 9,
                 "passed": true, "rationale": "..." } ],
  "passed":  true
}

On your own traffic

The offline gates are graded on our corpus against our oracle — rigorous, but not checkable by you. Two things close that:

# graded against a PUBLIC benchmark's answer key, not ours
distil retention --dataset hotpotqa

# A/B on YOUR traffic, with an A/A control
distil wrap --shadow claude
distil shadow-stats --record

Shadow mode replays the same compressed request to measure the grader's own disagreement with itself (A/A), then subtracts that floor from the A/B divergence. Without it you would read sampling noise as compression harm. --record emits the same envelope as the offline gates, so a live result is as attributable as a corpus one — with a traffic-window descriptor in place of a content hash, because live traffic has none and must never grow one.

Everything here is content-free. The meters store counts; no prompt, path or tool output leaves your machine.

What the probes found in our own work

An eval suite's real test is whether it catches its authors. These are recorded in full in the paper:

Each of those is a failure the probes exist to detect, occurring in the probes. We publish them because the alternative — a suite that has never been wrong — is the less trustworthy artifact.

Run it yourself

$ uvx --from distil-llm distil bench      # ~10s, no API key
$ uvx --from distil-llm distil fidelity   # the state probes
$ make gate                              # everything CI runs

Full methodology in EVALUATION.md; step-by-step instructions in RUNNING-EVALS.md.