context-report run manifest v0.1

This is the normative specification for context-report run's input: a manifest naming which artifacts to measure, against which agent, with which subject models, over which tasks, and how many runs per arm. The JSON Schema at schema.json is authoritative; this document explains what each field means. Where the two disagree, the schema wins and this file has a bug.

Purpose

A run manifest turns an efficacy claim (see ../../attestation/v0.1/attributes.md) from a one-off script into a reproducible design: the same manifest, run again with the same seed, exercises the same subjects over the same tasks with the same arm counts against the same target. It is metadata only — how an artifact actually works (what a plugin's hooks are, what an AGENTS.md says) is read from the artifact itself, never declared here.

Top-level fields

Tasks

A task is {id, prompt, subjects?, rules?, criteria?}:

Tasks as eval cases

tasks may instead name a directory: the claude plugin eval case layout plugin developers already write, from Claude Code's early-access reference. context_report.run.evalcases compiles it into exactly the task list above — the JSON tasks file above is the compiled form; nobody is expected to hand-write it. examples/evals/ is a worked example; the worked run.json points tasks at it.

<tasks-dir>/                      # the manifest's `tasks` value, e.g. "evals"
  <case-name>/
    prompt.md                     # YAML frontmatter + the prompt body
    graders/<grader-name>.md      # YAML frontmatter with `type` + fields; body may hold a rubric
    case.yaml                     # optional: setup, history replay — ignored in v0.1
  mocks/<server>/<tool>.md        # optional — ignored in v0.1

One case directory is one task; its directory name is the task's id. prompt.md and each grader file are parsed as YAML frontmatter (the block between the first two --- lines) followed by a body; a missing or malformed frontmatter block is a manifest error naming the file.

Case field Task field Notes
prompt.md body prompt Frontmatter stripped, whitespace trimmed.
prompt.md frontmatter plugins subjects Each entry is a path relative to the case directory, resolved and matched against a subject's own resolved path. A plugins entry matching no subject is a manifest error naming both the entry and the known subjects. No plugins → every subject, same as a JSON task with no subjects.
prompt.md frontmatter tags rules + tags A tag of the form rule:<id> contributes <id> to rules; every other tag is kept verbatim on the new Task.tags (the v0.1 runner ignores it).
prompt.md frontmatter runs runs Recorded on the new Task.runs. arms.nPerArm is still what v0.1 runs — a per-case runs is honoured only by the vendor's own runner.
graders/*.md with type: llm criteria The grader's criteria text binds to the rule id in its own rule: frontmatter field if it has one; otherwise to every rule id the case names via rule: tags; otherwise (no rule: tags at all) it is kept under the key "*".
graders/*.md with type: regex and target: last_message (or no target) Compiled to a deterministic Checker (GraderRegex in efficacy/fastjudge.py), not into criteria. context_report.run.evalcases.checkers_for(tasks) builds them; wiring them into efficacy.grade.grade is left to whoever assembles the run (see below).
everything else vendor_graders type: tool_used, tool_order, file_exists, baseline; a regex grader whose target is not last_message (e.g. mock_calls, trace, files). v0.1's runner is single-turn with no tool trace, filesystem diff, or baseline run to check these against, so they are recorded on the new Task.vendor_graders (so a row can say which graders it skipped) rather than errored on.
case.yaml, mocks/ Ignored in v0.1 (setup/history replay and mock tool responses need a multi-turn vendor runner); their presence must not raise an error.
prompt.md frontmatter name, max_turns, timeout_seconds, allowed_tools, model, append_system_prompt, env Vendor-runner-only: v0.1 runs one prompt against one target agent per manifest, so there is no per-case turn budget, tool allowlist, or model override to carry. name is not used either — the case directory name is the task id.

The criteria["*"] key is an extension of cards_for's consumer contract (src/context_report/run/cards.py): it means "the default criterion for every rule of this task's named subjects that has no more specific entry". cards_for does not read it as of this writing — it resolves a scenario's criterion as task.criteria.get(rule.id, rule.text) — so an eval case with llm graders but no rule: tags gets the rule's own text as its criterion until cards_for is extended to fall back to criteria.get("*") before rule.text.

What this manifest deliberately does not contain

Every run is kept

Each context-report run writes a new directory, out/runs/<run id>/, and never overwrites an earlier one: the id is a UTC timestamp (20260906T121500Z) unless --run-id NAME names it. out/runs.json indexes every run (id, start and end, the resolved manifest's digest, each (subject, model) result and estimate) and out/SUMMARY.md lays them side by side, one column per run — the view a developer reads to see what an edit to the artifact moved. context-report compare OUT --history prints the same table; compare OUT and judge OUT act on the latest run unless --run ID names another.

Resuming an interrupted run

context-report run MANIFEST --resume continues the latest run under out (or --run-id ID) instead of starting a new one: a (subject, model) pair whose statement exists is not run again, and a pair with transcripts but no statement reuses each transcript whose input_sha256 and model still match, calling the model only for the trials that are missing.

Output layout

Running a manifest with "out": "reports/" produces:

reports/
  runs.json                         # index of every run of this manifest
  SUMMARY.md                        # every run side by side, one column per run
  runs/<run id>/
    manifest.json                   # the resolved manifest, for provenance
    <subject id>/
      <model slug>.json             # the statement for this subject and this model
      <model slug>/
        transcripts/                # one file per (task, rule, arm, trial) recorded
      statement.json                # only when `models` is empty: one deterministic statement
    SUMMARY.md                      # this run's table across every subject and model

A directory holding manifest.json and subject directories directly (the layout before runs were kept) is still read as a single run by judge and compare.

A model slug is <provider>--<id> with every character outside [A-Za-z0-9._-] replaced by _ (e.g. anthropic/claude-sonnet-5 slugs to anthropic--claude-sonnet-5) — the same rule ModelRef.slug applies, so a filesystem path never needs to round-trip through JSON to be read back.

context-report judge OUT --judge <provider>/<id> re-grades recorded transcripts after the fact and writes a .judged.json sibling next to each statement it touches (e.g. <model slug>.judged.json), rather than overwriting the original: a statement is immutable once produced, and judging it again under a different judge is a new claim, not an edit to the old one.

Where the arms go

See ../../attestation/v0.1/README.md for how the transcripts this layout writes relate to the efficacy row a statement ends up carrying.

Versioning

Per in-toto convention, 0.X versions are major: fields MAY be added, removed, or change meaning between 0.1 and 0.2 without notice.