Your first project
Your own task evaluated end to end.
Scaffold
arena init projects/my_project --name my_project
That writes config.yaml, tests.yaml and scorers/. It runs immediately:
arena evaluate --project projects/my_project
CI asserts that, so a scaffold that does not run is a build failure.
Describe the job
config.yaml is the whole contract. Work through it in this order — it is the
order the decisions actually depend on each other.
1. What competes
models:
- key: sim_small
model: mock:small
card: {input_usd_per_mtok: 1, output_usd_per_mtok: 5}
- key: haiku
model: claude-haiku-4-5
- key: gpt5_mini
model: gpt-5-mini
Keep at least one mock: model while you are building. It costs nothing and
gives you a fixed point to check your scorer and weights against.
A model whose API key is missing is skipped, not failed — the run continues without it. So this config works before you have any keys.
2. What the model is told
defaults:
system: "Reply with exactly one label."
max_tokens: 8
temperature: 0
temperature: 0 matters for evaluation: you are measuring the model, not its
sampling variance. Use run.trials to measure variance deliberately instead.
3. How answers are graded
scorers:
default: classification
options:
classification:
labels: [billing, technical, account, other]
Ten builtins are available; arena scorers lists them. See
../reference/scorers.md to choose.
4. What "best" means
This is the part that is actually your job.
metrics:
weights: {accuracy: 0.55, cost: 0.25, latency: 0.20}
cost: {budget_usd_per_1k_calls: 2.0}
latency: {target_ms: 800}
Ask concretely: at your volume, what does one percentage point of accuracy buy, and what does it cost? A classifier handling 100,000 tickets a month has a very different answer from one handling 200.
budget_usd_per_1k_calls and target_ms are absolute reference points. Without
them, minmax normalization scores the cheapest model in the run at 1.0 even if
it is over your budget.
5. What is non-negotiable
constraints:
min_accuracy: 0.70
max_latency_p95_ms: 4000
A model violating one of these is DISQUALIFIED, not ranked low. This is the
most under-used part of the config and the most valuable: it converts "we should
probably not use that one" into a result the leaderboard enforces.
Set the floor at the level where you would genuinely refuse to ship, not at the level you hope for.
Write cases
tests.yaml:
tests:
- id: double_charge
input: "I was charged twice for the same order this month."
reference: billing
tags: [billing, easy]
- id: ambiguous_refund
input: "The app crashed and now I want my money back."
reference: billing
tags: [billing, hard, ambiguous]
Advice that matters more than the config:
- Write the ambiguous ones. Easy cases every model gets right carry no information about which model to pick. The cases that discriminate are the ones you are unsure about.
idis a database key. Renaming one detaches it from its own history.- Tag by difficulty and by topic. Then
--tags hardtells you where models actually differ. - Twelve cases is a demo, not an evaluation. Enough cases to separate two models is usually dozens. The leaderboard will tell you when it cannot.
Validate, plan, run
arena validate --project projects/my_project
Checks config, test files and credentials. Catches a typo in a second rather than after a partial spend.
arena evaluate --project projects/my_project --dry-run
Prints the full matrix and a cost estimate without making a call. Always do this
before the first paid run — trials × cases × models grows faster than people
expect.
arena evaluate --project projects/my_project
Iterate
arena history --project projects/my_project # runs over time
arena history --project projects/my_project --flaky # unstable cases
--flaky is the one that improves your test set. A case that flips between runs
on the same model is either genuinely ambiguous or badly specified, and either
way it is adding noise to your ranking.
If the leaderboard says your top two are too close to call, it means it — add cases rather than reading the third decimal place.