Astrometron
Reproducible measurement and evidence for AI systems.
Your benchmark says Model A scores 87%. Astrometron records exactly which dataset, model, prompt, tools, scorer, judge, failures and environment produced that number, and it keeps every failed case in the accounting.
Without Astrometron With Astrometron
"92% success" PASS 840
TASK_FAILURE 80
(80 failed API calls INFRA_ERROR 80
silently dropped) TOTAL 1000
Reported success:
84.0% of requested cases
No evaluation number without its evidence.
Five minutes, no API account
pip install astrometron # not yet on PyPI: pip install -e . from a clone
cd examples/support-routing
astrometron run eval.yaml
ASTROMETRON RUN
Run: 01M450Y35HWND01M5ZY0B0D157
Dataset: support-routing-v3 (sha256:0b00d24e02aa…)
Cases: 200
Provider: mock/mock-v1
Scorer: classification-v1
PASS 158
TASK_FAILURE 26
MODEL_ERROR 4
INFRA_ERROR 12
TOTAL 200
No rows omitted.
Pass rate: 79.0% of 200 (requested) 95% CI [72.8%, 84.1%]
conditional: 85.9% of 184 scored (PASS / (PASS+TASK_FAILURE)) - not the headline number
Cost: $0.003727 total, $0.00001864/case (price table)
Receipt:
.astrometron/runs/01M450Y35HWND01M5ZY0B0D157/receipt.json
The provider outage (12 cases) is INFRA_ERROR, not a model failure, and it stays in
the denominator.
What can I ask of a number?
| Question | Where the answer lives |
|---|---|
| Which exact dataset revision, and which rows? | dataset.revision (content digest), selected_case_ids, per-case row_digest |
| Which model, provider and version actually answered? | provider.identity, served_models (substitution is warned) |
| Which prompt, tools, scorer and judge? | prompt.digest, tools[].version/code_digest, scorer.code_digest, judge |
| Which environment and seeds? | environment, code.git_commit/git_dirty, run.seed |
| Which failures were included, and which were infrastructure? | aggregate.counts across 11 typed outcomes |
| Did any failed row silently disappear? | astrometron verify: requested = accounted, re-derived |
| Can it be reproduced? | astrometron reproduce RUN_ID, with an honest verdict |
Reproduce, compare, verify
astrometron reproduce latest
# Verdict: REPRODUCED (pinned, deterministic: every case identical; --repeats N)
# Verdict: NOT_EXACTLY_REPRODUCIBLE EXACT REPRODUCTION NOT POSSIBLE: provider model
# revision is not externally pinned. (re-runs everything it controls anyway)
# Verdict: INPUT_MISMATCH scorer changed (function_digest)
astrometron compare RUN_A RUN_B
Real output from the bundled demo (mock model vs keyword classifier):
Comparability: STRONGLY_COMPARABLE
PASS RATE (each on its full denominator 200 / 200)
79.0% -> 95.0% +16.0 percentage points
Paired on 184 cases scored in both runs: +8.7 pp 95% CI [+2.6, +14.8] McNemar p=0.009041
Improved cases: 25
Regressed cases: 9
Not scored in both: 16 (errors/cancellations: not model verdicts, excluded from the paired test)
Outcome transitions:
INFRA_ERROR -> PASS 12
MODEL_ERROR -> PASS 4
PASS -> TASK_FAILURE 9
TASK_FAILURE -> PASS 25
The headline moved 16 points. On the 184 cases that both runs actually scored, the
model-versus-model difference is +8.7 points. Much of the rest comes from 16 cases where
run A's provider failed: they count against A's headline, but they are kept out of the
paired test because an outage is not a model verdict. If the judge, scorer or
dataset had changed too, the label would be PARTIALLY_COMPARABLE with the reason
listed (e.g. judge model also changed: not a clean model-only comparison).
astrometron verify RUN_ID # VALID | STALE (config/data changed since) | INVALID (+ why)
Does it actually catch things?
Astrometron evaluates itself. astrometron selfcheck runs 40 adversarial scenarios. Each
one pairs an honest control (which must verify) with a deliberately broken evaluator or
forged receipt (which must be caught by a named rule):
[PASS] A3 failed rows deleted from cases AND selection, fully resealed
[PASS] F2 harness swallows the outage and returns an empty answer
[PASS] J2 judge failures relabelled as TASK_FAILURE
[PASS] R3 forged better-looking evidence offered to the router
...
40/41 scenarios passed
A mutation gate (tools/mutation_gate.py) breaks each critical invariant on purpose:
66 mutants, 65 killed, 1 equivalent with a written proof. See
docs/TESTING.md.
Write an eval
name: support-routing
task: classification # task class, used by the router
dataset:
path: support-tickets.jsonl # JSONL or CSV; content-addressed
id: support-routing-v3
# revision: sha256:… # pin: refuse to run if the data changed
# exclude: [{case_id: tkt-0042, reason: "label disputed"}]
prompt:
system: "Answer with one word: billing, technical, account or shipping."
template: "{input}"
provider:
kind: openai-compat # or: mock | python
base_url: http://127.0.0.1:8080/v1 # llama.cpp, Ollama, LM Studio, vLLM, hosted APIs
model: qwen2.5-7b-instruct
api_key_env: MY_API_KEY # literal keys are refused
scorer:
kind: classification # exact | normalized | regex | numeric | json_fields | python | judge
labels: [billing, technical, account, shipping]
run: {seed: 0, concurrency: 8, timeout_s: 60, retries: 2}
pricing: prices.yaml # or omit: cost is then UNKNOWN, never invented
Agents and tools: point solver at tool_loop or your own function. Every model call
and tool call (name, version, argument digest, result digest, status, timing) is recorded
in the case's trajectory. See examples/agent-tools.
Routing, but only on evidence
Once there is trustworthy evidence, astrometron route policy.yaml picks a model:
selected: python/keyword-router-v1
evidence:
run 01M450Y3QBABGKD9KJ8FREP8HR (0.0 days old)
receipt sha256:07610bd5…
success: 95.0% (95% CI 91.0-97.3%, wilson_lower used)
cost: $0.000000/case
cases: 200
candidates:
mock/mock-v1 BELOW_QUALITY_FLOOR
- BELOW_QUALITY_FLOOR: success wilson_lower 0.728354 < 0.85
- UNRELIABLE: error rate 0.08 > 0.05
mock/mock-large ELIGIBLE
* python/keyword-router-v1 ELIGIBLE
The router fails closed:
NO ELIGIBLE MODELwhen no candidate meets the constraints;ROUTING EVIDENCE STALEwhen the evidence is old or the model revision changed;COST_UNKNOWNunder a cost limit;- tampered evidence is skipped. See docs/ROUTING.md.
Docs
Architecture · Datasets · Receipt schema · Failure taxonomy · Reproducibility · Comparability · Routing · Threat model · Testing · Performance · Ecosystem
Scope
Astrometron is not:
- an observability dashboard;
- a prompt manager;
- an agent framework;
- a leaderboard;
- a hosted service.
It sits beside your eval runner (Inspect, promptfoo, MLflow, your own scripts) and makes its numbers accountable. Its only runtime dependency is PyYAML.
Apache-2.0. Contributions welcome: see CONTRIBUTING.md.
Metadata
Release files for astrometron 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| astrometron-0.1.0.tar.gz | 134.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| astrometron-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 215.9 kB
Release files / astrometron-0.1.0.tar.gz
| Download URL | astrometron-0.1.0.tar.gz |
|---|---|
| Size | 134.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f2dceb9c47186d91e9cb6b89394e42ebbef9b776f62d2ec7c8d89e0b7c8f1ddf
|
|
BLAKE2b-256 checksum How to use checksums |
d6dd8d77a8d5fe29343810ffeeb60050584398cb34336ea9c5344ca040702702
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|
Release files / astrometron-0.1.0-py3-none-any.whl
| Download URL | astrometron-0.1.0-py3-none-any.whl |
|---|---|
| Size | 81.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
62a6763b4a18483c1f0cae7f84e21da79e191bd73e97d779bc37a1ca5f75e535
|
|
BLAKE2b-256 checksum How to use checksums |
6f4af188b6a43105c1a896793073f052f3434d129f2e1b667f3f463311532124
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|