Repository navigation
evaluation-harness v0.5.0
Two things to know before upgrading: the package now needs Python 3.10 or later, and it depends on the
official MCP SDK (mcp>=2.3,<3). On 3.9, stay on v0.4.4. What you get: evh mcp, the project's read
commands as tools for any agent (stdio on your machine, or --http for a team or the public), and a dataset page
that shows what is inside a dataset without republishing its text. UPGRADING has the steps.
Added
-
evh mcp: the project's read commands as MCP tools, for an agent (stdio, the official SDK,
RFC-018). Thirteen
tools:overview,datasets,dataset-items,arms,goals,references,metrics,activity,
search,inspect,rounds,runs-list,validate. Each call runs the command as a child process, so a tool
answers what the command prints; each is annotated read-only; no tool writes project data, spends or runs a
model (the one file a call writes is the p cache in the project's ignored.cache/, below). For Claude
Code, in the project:claude mcp add evh -- evh mcp. -
evh mcp --http: the same server for a team, or for anyone. Streamable HTTP, stateless, JSON responses:
clients on every protocol revision since 2025-03-26 call tools without a session. The Host is checked against DNS
rebinding (--allowed-hostis required to bind beyond the loopback);--publicleaves out the options that
answer with an item's text, and thevalidatetool;--rate,--concurrentand--timeoutbound the load; a
missing required argument is refused before anything runs;--hookstakes a deployment's error reporting,
tracing, metrics and analytics, in the inspector's shape. Each call is logged as one JSON line: the tool, its
arguments' names (never their values), how it ended, its time; never the caller's address or user agent.
--ratecounts calls a minute per caller (an IPv6 caller by its /64),--rate-totalfor all callers together;
over HTTP a caller has one call running at a time. -
EVH_P_CACHE: the goals' paired tests kept on disk. Everyevhcommand is a fresh process, so a view that
shows the goals drew the same tests again each time (about 50 s on this repository's runs). With the variable
naming a file, each estimated p is stored and read back exactly;evh mcpsets it to the project's ignored
.cache/.goals,overviewandarmsanswer in under two seconds after the first. -
This repository's MCP server,
https://evals.humancopilot.eu/mcp(the deployment, not the package): the six
worked examples for any agent, read-only, without sign-in. -
--jsonon the catalog's view commands (overview,datasets,arms,goals,references,metrics,
activity,search,inspect): one object,{"command", "version", "result": {"blocks": [...]}}, each table as
columns and rows of values, every other line as text. It is collected from the same calls that print the text, so
the two cannot disagree. 0.7.0 gives each command its own result schema and retires this generic form: do not
build onblocks. -
What is inside a dataset, on its page and as a command.
evh dataset-profile --dataset-id D,
run where the dataset is materialized, writesdata/profiles/D.json(schemas/profile.schema.json,
checked by validate V1): the number of items and their words, the shape of each gold reference
(labels and how many of each, entries per item, a record's fields, or words per reference), and
per item its length, its gold and, when known, where it can be read at its source. A gold of labels
(a few short values that repeat) is listed per item; any other gold is summarised, never listed. It
holds no item text, which a dataset's licence may not let you redistribute, unless written with
--with-text, which is refused unless git has committed every item's file (a project's own synthetic data). Commit the profile and the dataset page shows "what's
inside" wherever the catalog is built;evh dataset-items --dataset-id D [--item ID]prints the
same, and an item's text when it is materialized here. -
An item's link to its source comes from the dataset: an item's
source_url, or a
data/sources/<dataset_id>.rows.jsonthat a fetch or import step writes ({"source", "item_url", "rows": {item_id: {"row", "sha256"}}}), matched by the item's bytes, so an id that recurs in
another dataset never takes its link. This repository's Hugging Face fetchers write one, so each
item opens at its row in the dataset viewer. -
inspect.html?…&item=IDopens that item once the comparison has loaded; a dataset page's
inspect link uses it. -
Adapter:
dataset_profile(dataset), optional (RFC-003). -
Every catalog page names its command, in its footer (
evh goals --goal G, the inspector's from the arms
picked).evh catalog --mcp URLnames the same as an MCP tool call, with the server and how to connect
(--mcp-howto); this repository's catalog passes its/mcp, and its header links to the site's MCP section. -
evh dataset-profile --jsonandevh dataset-items --jsonprint the profile inside the same envelope as
the views,{"command", "version", "result": <the profile>}; the Makefile targetsdataset-profileand
dataset-items.
Changed
- Python 3.10 or later. The official MCP SDK needs it, and every feature comes with one install, so the
package's floor moves from 3.9 to 3.10 (CI tests 3.10, 3.11 and 3.14). On 3.9, pinv0.4.4. - A new dependency:
mcp>=2.3,<3, which bringsanyio,httpx2,mcp-types,opentelemetry-api,
pydantic>=2.12,pyjwt[crypto],python-multipart,sse-starlette,starlette,typing-extensions,
typing-inspectionanduvicorn. No result moves. - A dataset id is a name, never a path. Every command that reads or writes
data/…/<id>.jsonrefuses an id
that is not letters, digits,.,_and-(or that holds..); before,../xread or wrote outsidedata/. - A param shown on a catalog page or by
evh arms --armhides a URL's user and password and a nested key
named like a credential (headers: {Authorization: …}), as it already hid a top-levelapi_key.
Security
From two independent reviews before this release, each reproduced before it was fixed:
- The inspector served any
.txtunder its data tree through/api/item?item=../../…(an item id was never
checked; onmainsince 0.4.0). An item id is now a file's name, refused otherwise, in the server and the adapter. - The inspector's answers were cached by count, not size: a hundred distinct 40-arm questions took a 256 MiB
container past its limit, and--warmalone did on this repository's data. The cache is bounded by the bytes it
keeps (32 MB), keeps no refusals, takes no question over 4 KB, and--warmstops when it is three quarters full;
the deployment's inspector has 384 MB (it peaked at 228 of 256 under load). Its rate key is an address, never a header's
text. evh mcp --http: a few parallel 4 MiB bodies killed the server; held GETs and ping floods cost memory no rate
saw. Now--max-body,--max-connections, 8 open requests per caller, a 10 s deadline on a request's body
(eight callers holding slow bodies took every connection),--request-rate, GET is 405, refusals do not count toward
--rate-total, and--publicanswers are kept and their errors carry no paths. Values with NUL bytes or over 512
characters are refused.- The deployment: Caddy refuses a body over 64 KB on
/mcp(and itshandleline now adapts); nginx limits
each visitor (20 requests a second, a burst of 200, 32 connections); the deploy checks the inspector is up and
under 80% of its memory once--warmis done, and rolls back otherwise; the MCP hooks no longer export the
SDK's own spans; the containers'
/tmpcaps files as well as bytes;render.sh's check for reference texts in a release could pass with 1,657 of
them (a pipe's SIGPIPE) and refused the five tracked ones: it now compares against what git tracks; a deploy keeps
two releases (the live one and the rollback), prunes at its start as well as its end, and refuses to start short of
2 GB or 300,000 inodes; the upload hard-links unchanged files again (no file ever linked: each release took ~69k new
inodes).
Fixed
- The deployment's release ships
data/models.yaml(render.sh, and the item fields and profiles with it):
from 0.4.2 it was never copied, so the deployed arms page called every model's weights unknown.
tests/test_render_data.pyfails when a part ofdata/the adapter reads is not copied. - The CNN/DailyMail and SummEval fetchers deleted the committed gold
manifest.jsonon every refetch; they
now clear only the item files. --hooksfiles with a module-level@dataclassfailed to load (the module was not registered before it
ran); they load as any other module now.- A page links only to
http(s)URLs: asource_urlor rows map that heldjavascript:…was written into
anhref. - The p cache reads an unreadable or non-UTF-8 file as no cache instead of failing the command, starts a new
line after a torn one, and keys each p by how it was computed. evh dataset-profile --with-textcounts a file as tracked only once it is committed, not when it is
merely staged.
Install
pip install "evaluation-harness @ git+https://github.com/chipi/eval-harness@v0.5.0#subdirectory=harness"