- Passive — the world is a frozen memory2 recording. Deterministic, cheap, repeatable. Run these constantly.
- Interactive — a live robot or sim; actions change the world; scoring samples the live memory2 store while the agent works.
Streams, and interactive scoring reads a real Store.
Quick start (CLI)
results.jsonl, summary.json,
and per-case transcripts to ~/.local/state/dimos/evals/run-*/.
Your first eval, end to end
Build a tiny recording (any memory2 store works — this is the same API the robot’s Recorder uses; seedimos/memory2/intro.md for the full Stream API):
session=evals ansi=false no-result
session=evals ansi=false
context is a tuple of callables that
receive the opened Store and return the mem2 Streams the model may see —
anything the Stream API expresses (windows, filters, single frames) works, and
the runner evenly downsamples each selected stream to context_budget
observations before encoding:
session=evals ansi=false no-result
chat_model= injects any LangChain chat model — here a canned fake so
this document runs offline; drop the argument to use the production model
config (gpt-5.6-luna, same construction as the deployed McpClient):
session=evals ansi=false
Scoring
Scores are floats in[0, 1]; passed = score >= threshold. Scorers are
plain functions (expected, got) -> float — a custom heuristic is a lambda,
not a class:
session=evals ansi=false
exact— equality (the default). Pair with a parser (yes_no,choice,int) so formatting noise doesn’t fail a correct answer.within(band)— graded numeric credit: 1.0 exact, 0.5 halfway, 0 outside.ramp(distance, band)— same ramp over meters; msg types support arithmetic, so physical scorers stay one-liners:lambda s: ramp((GOAL - s.streams.odom.last().data.position).length(), band=0.5)judge(rubric)— LLM-as-judge with partial credit, wrapping the langchain/openevals standard (inputs/reference_outputsconvention, so external VQA benchmarks map on natively).
interval_s); aggregate
reduces it:
session=evals ansi=false
final = “where did it end up”, floor = “never left the zone”,
mean = “how good was it throughout”.
Interactive evals
The case names its environment (reproducibility);score reads the live
store the robot’s Recorder writes, sampled every interval_s:
session=evals ansi=false no-result
(t, score) series — “reached the bed at t=50s
and stayed” and “grazed it at the deadline” score differently under floor
vs final.
Running
- CLI:
dimos evals run <dotted.suite> [--tags nav --blind --limit 5 --model gpt-4o] - Python:
EvalRunner(...).run(SUITE, tags=frozenset({"encoding"})) - pytest: suites are importable lists —
@pytest.mark.parametrize("case", SUITE)and assert onpassed(gate live-model tests withskipif_no_openai). - MCP: the
EvalModuleskillsrun_evals/list_eval_suitesreturn the summary + run dir, so a coding agent can run evals, grep transcripts, edit prompts/encodings, and run again. - Blind ablation:
EvalRunner(blind=True)withholds all observations. A case that still passes blind is guessable — fix its distractors. Run every new suite sighted and blind once before trusting it. - Preflight: before anything runs, every case is checked against the rig —
a missing stream fails with
"No stream 'lidar'. Available: [...]", a case needing MCP/sim fails with what’s missing. Errors are per-case; one broken case never kills a run.
