# Mira

**Navigation:** [Products](/products/) | [Use Cases](/use-cases/) | [Blog](/blog/) | [Docs](https://docs.everruns.com/) | [GitHub](https://github.com/everruns/mira)

---

> Your evals belong in your repo.

Mira is a code-first eval framework for agents and tools, built for multi-turn, tool-using, long-running trajectories. Named after the Ukrainian *міра*, measure, because that is what an eval framework is for. Datasets, the study, and every saved run are just files you commit.

```bash
brew install everruns/tap/mira
```

- **30+ scorers**: built in, composable
- **Any language**: Rust, Python, TS SDKs
- **MIT**: licensed, in Rust

---

## A dataset, a subject, some scorers, crossed with your matrix.

Four entities and one line of algebra. Everything else in Mira, selection, resume, reporting, is the host doing bookkeeping on top of them.

```text
Eval = Dataset(Sample…) + Subject + [Scorer…]  ×  model matrix × axes
```

### `Sample`: the input

One dataset row: input turns, an optional `expected`, seeded `files`, `tags`, and `metadata`. Language-agnostic JSON, write it inline for small evals, or load `Dataset::jsonl` for larger sets.

### `Subject`: the thing under test

One adapter per *shape*: an in-process closure, an external binary, or a live runtime session. Every one produces the same normalized `Transcript`, so scoring and reporting are shared.

### `Scorer`: the verdict

`score(&Sample, &Transcript) -> Score`, a value in `0..1`, a pass, and a reason. Deterministic built-ins, operational budgets, closures, and LLM-as-judge all share the one trait.

### `Target`: the axis

One matrix case, and the first-class axis: a `(label, provider, model, available)` tuple with no API keys and no SDK types. For an agent eval the target *is* the agent, `Target::cli("yolop")`.

---

## Your evals and the runner are two processes.

Eval definitions and the runner sit on opposite sides of a process boundary, talking newline-delimited JSON over stdio. That single decision is why a study can be written in any language, and why your provider keys never leave your own program.

- **Study: Your eval program.** It owns subjects and scoring, and knows nothing about selection, matrices, saved runs, or rendering. Provider API keys live only here and never cross the wire.
- **Host: The `mira` CLI.** It spawns the study, enumerates evals, plans selection × matrix, drives execution with bounded per-provider concurrency, then aggregates, saves the run, and renders.
- **Protocol: Three methods, versioned.** `initialize`, `list`, and `run`, plus fire-and-forget `event`/`log` notifications and capability-gated extensions. Every payload tolerates unknown fields, so a newer study and an older host interoperate.

> Selection only ever subsets the grid the study declared, the host never adds cases.
>
> - docs/how-it-works.md

---

## One open vocabulary, freely composed.

Scorers grade three surfaces: the final response, the structured [ATIF](https://github.com/harbor-framework/harbor/blob/main/rfcs/0001-trajectory-format.md) trajectory, and the operational metrics. A case passes only if every applicable score passes, so a cost regression fails as loudly as a wrong answer.

- **Text & output** (the final response): `succeeded`, `non_empty`, `contains`, `not_contains`, `equals`, `regex`, `matches_expected`, `json_valid`, `json_field_equals`
- **Tools** (what was called): `tool_called`, `tool_not_called`, `tool_calls_within`, `tools_used_exactly`, `tool_called_before`
- **Trajectory** (arguments & observations): `tool_called_with`, `tool_arg_matches`, `observation_contains`, `steps_within`
- **Budgets** (tokens, cost, latency): `tokens_within`, `output_tokens_within`, `cost_within`, `turns_within`, `latency_within`, `ttft_within`, `metric_within`, `metric_at_least`
- **Files** (the captured workspace): `file_exists`, `file_contains`
- **Compose** (anything above): `all_of`, `any_of`, `not`, `scorer`, `model_graded`

A scorer that cannot run, no API key, a rate limit, a 5xx, returns **N/A** instead of a misleading fail: excluded from the verdict and the aggregate, rendered `–`, never counted as a JUnit failure. That is how a run with no credentials still comes back green.

---

## Three shapes, one transcript.

A subject is whatever you point the eval at. Pick the adapter that matches its shape, the rest of the framework does not change.

### In-process

A closure. The shortest path for prompt, library, and model evals, no process boundary, no serialization.

`subject_fn(|sample, cx| …)`

### Any binary

The polyglot path: evaluate an external program in any language. It reads stdout, an ATIF trajectory file, or a canonical JSONL transcript, and can seed and capture files.

`CliSubject::new("./agent")`

### Live runtime

Drives a real [Everruns Runtime](/products/runtime) session, so long-running multi-turn agents are evaluated as they actually run.

`mira_everruns::RuntimeSubject`

---

## A study is a program that speaks the protocol.

The contract is the protocol, not a list of blessed languages: any program that speaks newline-delimited JSON over stdio is a first-class study. Rust is the native path, and the Python and TypeScript SDKs are not bindings, they are standalone libraries generated from the same JSON Schema the Rust host is, so they cannot drift from the wire format.

### Rust (`study.rs`)

```rust
use mira::scorer::{contains, succeeded, cost_within};
use mira::subject::subject_fn;
use mira::{eval, Eval, Transcript};

#[eval]
fn greet() -> Eval {
    Eval::new("greet")
        .sample("hi", "Say hi and tell me the answer to life.")
        .subject(subject_fn(|sample, cx| async move {
            Transcript::response(call_model(cx.target(), sample).await)
        }))
        .scorer(succeeded())
        .scorer(contains("42"))
        .scorer(cost_within(0.01))
        .build()
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
    mira::Study::registered().serve().await
}
```

```bash
mira run --study study.rs
```

### Python (`study.py`)

```python
import mira

study = mira.Study("my-evals", version="0.1.0")

@study.eval(
    name="greet",
    samples=[mira.Sample("hi", prompt="Say hi and the answer.")],
    targets=[mira.target("sim")],
    scorers=[mira.succeeded(), mira.contains("42")],
)
def greet(sample, cx):
    response = call_model(cx.target, sample.text)
    return mira.transcript(response, usage=mira.Usage(output_tokens=8))

if __name__ == "__main__":
    study.serve()
```

```bash
mira run --study study.py
```

### TypeScript (`study.mjs`)

```typescript
import { Study, sample, target, succeeded, contains, transcript }
  from "mira-eval";

const study = new Study("my-evals", { version: "0.1.0" });

study.eval({
  name: "greet",
  samples: [sample("hi", { prompt: "Say hi and the answer." })],
  targets: [target("sim")],
  scorers: [succeeded(), contains("42")],
  run: async (s, cx) => transcript(await callModel(cx.target, s.text)),
});

study.serve();
```

```bash
mira run --study-cmd "node study.mjs"
```

A single-file Rust study needs no `Cargo.toml`, dependencies go in cargo-script frontmatter and `mira run --study study.rs` resolves the runner from the extension. The Python SDK is pure stdlib; the TypeScript SDK has zero runtime dependencies.

---

## Every run is a folder you can commit.

There is no server to stand up and no third party holding your results. `mira run` saves a run folder by default, plain JSON your coding agent can read, diff, and chart without an SDK or a query language.

`results/<run_id>/`

- `meta.json`: Run identity: id, study, timestamps, summary, and the environment it came from.
- `report.json`: The canonical machine-readable record, summary plus every case.
- `report.html`: A self-contained, dependency-free transcript viewer you can open from a CI artifact.
- `cases/<key>/result.json`: One finished case (`eval/sample@target[…]#trial`), written atomically as it completes.

### Stamped with where it came from.

*Provenance.* Each run records the git commit, branch, and dirty flag, the box (`os`, `arch`, `cpus`, `mem`), the `mira` version, and auto-detected CI labels, so a number from three weeks ago is still interpretable today.

### Finish a run, do not restart it.

*Resume.* `--resume <run_id>` reopens a run folder, skips the cases already recorded, and runs only what is missing. A fresh run mints a new id and reuses nothing, no silent reuse of stale results.

### Reports without re-execution.

*Re-render.* `mira report <run_id>` re-renders from the stored case files with no study process spawned, and `mira export <run_id> --format atif` emits standalone ATIF trajectory documents.

---

## Drops straight into CI.

Non-zero exit when a case fails, one flag per artifact, and a progress bar that hides itself under a non-TTY so it never pollutes logs.

- `--format json`: canonical record
- `--format jsonl`: one case per line
- `--format csv`: long-format, for analysis
- `--format junit`: CI test reporting
- `--format md`: pull-request comments
- `--format html`: self-contained viewer

- **Green offline.** A target whose API key is missing is marked unavailable and *skipped*, not failed, so a fresh checkout runs clean before anyone hands out credentials.
- **Repetitions and variance.** `--trials N` runs each case N times for pass@k and standard deviation, with `--seed` for reproducibility.
- **Bounded concurrency.** `--provider-concurrency anthropic=2,openai=4` caps each provider, with adaptive throttling on rate limits.
- **Experiment plans.** Compare short-lived treatments, prompt candidates, two binaries, a dependency bump, from an external TOML plan with `mira run --experiment plan.toml`, without touching the durable study.
- **Diagnose the setup.** `mira doctor` lints `mira.toml`, the study listing, and the saved runs in one pass; `--fix` applies the safe repairs. Errors exit non-zero, so it can gate CI.
- **Agent-authored.** Mira ships an agent skill, so your coding agent already knows the API, the CLI, and the conventions, describe the eval you want and let it write the study.

---

## From nothing to a scored matrix.

1. **Install the host.** Homebrew on macOS & Linux, or grab the release binary with `cargo binstall mira-cli`.
2. **Write a study.** One file. `#[eval]` discovers it the way `cargo test` discovers tests, no registration, no config.
3. **Run the matrix.** The run saves itself to `./results/<run_id>/`. Commit the folder and point your agent at it.

```bash
$ brew install everruns/tap/mira
$ mira list --study study.rs
$ mira run  --study study.rs
# subset it like cargo test:
$ mira run  --study study.rs --tag smoke
# self-contained report for a CI artifact:
$ mira run  --study study.rs --format html --out report.html
✓ 18 passed / 20 scored (2 failed, 0 n/a, 6 skipped)
```

---

## Measure the agent you already have.

Mira is open source and MIT-licensed. Install the host, write one file, and keep the results in the repo where the code lives.

Install: `brew install everruns/tap/mira`

GitHub: https://github.com/everruns/mira
