Shared Context

Eval Set Authoring

Solves: turning "I know it when I see it" into examples and a standard a test can use.

Watching — a candidate behavior we are tracking, not yet a published pattern.

pattern: watching code: experimental <EvalSetAuthoring>: Experimental: a first core to try out, and it will change.

watching →emerging →established →contested →fading 10 sightings since Jul 2024 seen in 1 different contexts
Ask an assistant about this pattern Ask ChatGPT ↗Ask Claude ↗

Eval Set Authoring is how a person builds the examples a prompt is tested against. The examples come from real traffic, from generated cases, or from paste. Beside them the person writes the rubric: plain sentences that say what a good answer does and does not do. The rubric is a shared object, edited and versioned with the prompt.

When to Use It

  • A prompt drives behavior real humans see
  • Quality lives in someone's head and needs to be written down
  • You have real inputs to draw examples from

When Not To

  • The prompt is a one-off; a handful of manual checks is enough
  • Nobody can say what good looks like yet; run a few cases by hand first
  • The examples would expose private data with no plan to handle it

Three views

The exchange between the human, the agent and the system the agent acts on; who holds each part of it; and the component that implements it.

01 · the interaction
human agent system production logs · eval store describes the failure in plain words pulls matching cases from production logs returns candidate cases with their traces proposes cases, a rubric and examples accepts, edits or drops each case saves the set as a new version
02 · who holds what
human interface agent Source: Where the examples come from: real traffic, generated cases, or paste 01 Source Cases: The examples themselves, each with an input and, where known, a good answer 02 Cases Rubric: Plain sentences that say what a good answer does and does not do 03 Rubric Pass and fail examples: One answer that passes and one that fails, beside each rule 04 Pass and fail examples Version: The set and the rubric carry a version, beside the prompt version 05 Version
human interface agent Source: Where the examples come from: real traffic, generated cases, or paste 01 Source Cases: The examples themselves, each with an input and, where known, a good answer 02 Cases Rubric: Plain sentences that say what a good answer does and does not do 03 Rubric Pass and fail examples: One answer that passes and one that fails, beside each rule 04 Pass and fail exampl… Version: The set and the rubric carry a version, beside the prompt version 05 Version
  1. 01 Source Where the examples come from: real traffic, generated cases, or paste
  2. 02 Cases The examples themselves, each with an input and, where known, a good answer
  3. 03 Rubric Plain sentences that say what a good answer does and does not do
  4. 04 Pass and fail examples One answer that passes and one that fails, beside each rule
  5. 05 Version The set and the rubric carry a version, beside the prompt version
03 · the component · code: experimental

vendor presets are recreations, not vendor assets

THE LIVE PLAYGROUND NEEDS A WIDER SCREEN — IT'S WAITING ON DESKTOP. THE ANATOMY ABOVE AND THE CODE BELOW ARE THE SAME COMPONENT.

Do / Don't

Do

Put one passing and one failing answer beside every rule.

Don't

Ship a rubric that only its author can apply.

A rule with no examples is a preference. The pass and fail pair is what lets a second person, or a judge model, apply it the same way; the core exposes the rules still missing one as unsharedRules.

Do

Keep real traffic in the set and show each case's provenance.

Don't

Fill the set with generated cases and call it coverage.

Generated cases are cheap and miss how people phrase things: the lowercase double apology, the voice note that drifts to a birthday card. Provenance per case keeps the mix in view.

Do

Version the set and the rubric beside the prompt version.

Don't

Version only the prompt and let the set test last quarter's behavior.

A set written for last quarter tests last quarter's product. Three versions side by side say which standard a run was judged against and when a case aged out.

Every pair cites a documented tension or principle boundary — no free-floating taste.

Get the Code

Namespaced install (add the registry to components.json once):

{
  "registries": {
    "@agentsandhumans": "https://agentsandhumans.ai/api/registry/{name}.json"
  }
}
npx shadcn@latest add @agentsandhumans/eval-set-authoring

Or install directly from the endpoint:

npx shadcn@latest add https://agentsandhumans.ai/api/registry/eval-set-authoring.json

Dependencies: react · Files: patterns/eval-set-authoring/eval-set-authoring-core.tsx

patterns/eval-set-authoring/eval-set-authoring-core.tsx
/**
 * EvalSetAuthoring — headless core (primitive, watching).
 * Pattern: eval-set-authoring · family: shared-context · lifecycle: watching
 * Solves: turning "I know it when I see it" into examples and a standard a
 * test can use.
 *
 * Generated from the five-part anatomy on /patterns/eval-set-authoring. Zero
 * CSS beyond data attributes; the styled skin lands in patterns-ui when the
 * anatomy stabilizes (see /methodology).
 *
 * Anatomy (who holds what):
 *   01 Source   — human     — where examples come from: traffic | generated | paste
 *   02 Cases    — shared    — each case has an input and, where known, a good answer
 *   03 Rubric   — human     — plain sentences: what a good answer does / does not do
 *   04 PassFail — human     — one passing and one failing answer beside each rule
 *   05 Version  — interface — set + rubric versioned beside the prompt version
 */
import * as React from 'react';
import { createEvent, type EventDraft, type InteractionEventHandler } from '@/lib/interaction-contract';

// ---------- types ----------

export type EvalSource = 'traffic' | 'generated' | 'paste';

export interface EvalCase {
  id: string;
  input: string;
  /** Known-good answer, if the author has one. Optional on purpose. */
  ideal?: string;
  source: EvalSource;
}

export interface RubricRule {
  id: string;
  /** Plain sentence. "The answer names the refund window." */
  text: string;
  /** What makes the rule a shared standard rather than one person's taste. */
  pass?: string;
  fail?: string;
}

export interface EvalSetVersion {
  set: string; // e.g. "v3"
  rubric: string; // e.g. "v2"
  prompt: string; // the prompt version this set was authored against
}

export interface EvalSetState {
  source: EvalSource;
  cases: EvalCase[];
  rubric: RubricRule[];
  version: EvalSetVersion;
}

export type EvalSetAction =
  | { type: 'setSource'; source: EvalSource }
  | { type: 'addCase'; case: EvalCase }
  | { type: 'removeCase'; id: string }
  | { type: 'addRule'; rule: RubricRule }
  | { type: 'updateRule'; id: string; patch: Partial<RubricRule> }
  | { type: 'removeRule'; id: string }
  | { type: 'bumpVersion'; part: keyof EvalSetVersion; value: string };

// ---------- pure helpers (tested without React) ----------

export function reducer(state: EvalSetState, a: EvalSetAction): EvalSetState {
  switch (a.type) {
    case 'setSource':
      return { ...state, source: a.source };
    case 'addCase':
      return { ...state, cases: [...state.cases, a.case] };
    case 'removeCase':
      return { ...state, cases: state.cases.filter((c) => c.id !== a.id) };
    case 'addRule':
      return { ...state, rubric: [...state.rubric, a.rule] };
    case 'updateRule':
      return {
        ...state,
        rubric: state.rubric.map((r) => (r.id === a.id ? { ...r, ...a.patch } : r)),
      };
    case 'removeRule':
      return { ...state, rubric: state.rubric.filter((r) => r.id !== a.id) };
    case 'bumpVersion':
      return { ...state, version: { ...state.version, [a.part]: a.value } };
  }
}

/** Rules missing a pass or a fail example — the "one person's taste" failure mode. */
export function unsharedRules(rubric: RubricRule[]): RubricRule[] {
  return rubric.filter((r) => !r.pass || !r.fail);
}

/** True when no case came from real traffic — the "synthetic drift" failure mode. */
export function isSyntheticOnly(cases: EvalCase[]): boolean {
  return cases.length > 0 && cases.every((c) => c.source !== 'traffic');
}

/** A case set as a JSONL fixture — one object per line, blank lines ignored. */
export function parseJsonl<T = unknown>(text: string): T[] {
  return text
    .split('\n')
    .map((l) => l.trim())
    .filter(Boolean)
    .map((l) => JSON.parse(l) as T);
}

/**
 * The contract event an authoring action produces (spec 2026-10-04 §1), or null.
 * Adding or removing a case or a rule and switching the source are
 * review.recorded; a version bump is baseline.versioned, because the set the
 * next comparison runs against has changed. Keystrokes inside a rule
 * (updateRule) emit nothing; `onChange` carries every state.
 */
export function authoringEvent(before: EvalSetState, a: EvalSetAction): EventDraft | null {
  switch (a.type) {
    case 'setSource':
      return before.source === a.source
        ? null
        : { type: 'review.recorded', actor: 'human', payload: { kind: 'source', source: a.source } };
    case 'addCase':
      return { type: 'review.recorded', actor: 'human', correlationId: a.case.id, payload: { kind: 'case', change: 'added', case: a.case } };
    case 'removeCase':
      return before.cases.some((c) => c.id === a.id)
        ? { type: 'review.recorded', actor: 'human', correlationId: a.id, payload: { kind: 'case', change: 'removed', caseId: a.id } }
        : null;
    case 'addRule':
      return { type: 'review.recorded', actor: 'human', correlationId: a.rule.id, payload: { kind: 'rubric', change: 'added', rule: a.rule } };
    case 'removeRule':
      return before.rubric.some((r) => r.id === a.id)
        ? { type: 'review.recorded', actor: 'human', correlationId: a.id, payload: { kind: 'rubric', change: 'removed', ruleId: a.id } }
        : null;
    case 'bumpVersion':
      return before.version[a.part] === a.value
        ? null
        : {
            type: 'baseline.versioned',
            actor: 'human',
            payload: { part: a.part, from: before.version[a.part], version: { ...before.version, [a.part]: a.value } },
          };
    case 'updateRule':
      return null;
  }
}

// ---------- context ----------

interface Ctx {
  state: EvalSetState;
  dispatch: React.Dispatch<EvalSetAction>;
  /** Rules missing a pass or fail example — the "one person's taste" failure mode. */
  unsharedRules: RubricRule[];
}

const EvalSetCtx = React.createContext<Ctx | null>(null);

export function useEvalSet(): Ctx {
  const ctx = React.useContext(EvalSetCtx);
  if (!ctx) throw new Error('useEvalSet must be used inside <EvalSetAuthoring.Root>');
  return ctx;
}

// ---------- Root ----------

export interface RootProps {
  initial: EvalSetState;
  onChange?: (state: EvalSetState) => void;
  /** One agent run; stamped on every contract event. Defaults to a generated id. */
  runId?: string;
  /** Contract events: review.recorded, baseline.versioned. */
  onEvent?: InteractionEventHandler;
  /** The parts, in any order; a story or a skin composes them. */
  children?: React.ReactNode;
}

function Root({ initial, onChange, runId, onEvent, children }: RootProps) {
  const [state, apply] = React.useReducer(reducer, initial);
  React.useEffect(() => onChange?.(state), [state, onChange]);
  const unshared = React.useMemo(() => unsharedRules(state.rubric), [state.rubric]);
  const latest = React.useRef(state);
  latest.current = state;
  const fallbackRun = React.useId();
  const run = runId ?? fallbackRun;
  const dispatch = React.useCallback<React.Dispatch<EvalSetAction>>(
    (a) => {
      const draft = authoringEvent(latest.current, a);
      apply(a);
      if (draft) onEvent?.(createEvent(run, draft));
    },
    [onEvent, run],
  );
  return (
    <EvalSetCtx.Provider value={{ state, dispatch, unsharedRules: unshared }}>
      <section data-pattern="eval-set-authoring" data-lifecycle="watching" aria-label="Eval set authoring">
        {children}
      </section>
    </EvalSetCtx.Provider>
  );
}

// ---------- 01 Source ----------

const SOURCES: { value: EvalSource; label: string; hint: string }[] = [
  { value: 'traffic', label: 'Real traffic', hint: 'Keeps the set honest to how people phrase things.' },
  { value: 'generated', label: 'Generated', hint: 'Cheap and plentiful; can drift from real phrasing.' },
  { value: 'paste', label: 'Paste', hint: 'Fast start; no provenance.' },
];

function Source() {
  const { state, dispatch } = useEvalSet();
  const synthetic = isSyntheticOnly(state.cases);
  return (
    <fieldset data-part="source" data-actor="human" data-synthetic-only={synthetic || undefined}>
      <legend>01 · Source</legend>
      {SOURCES.map((s) => (
        <label key={s.value} title={s.hint}>
          <input
            type="radio"
            name="eval-source"
            value={s.value}
            checked={state.source === s.value}
            onChange={() => dispatch({ type: 'setSource', source: s.value })}
          />
          {s.label}
        </label>
      ))}
      {synthetic && (
        <p role="status" data-warning="synthetic-only">
          No case came from real traffic — the set can drift from how people phrase things.
        </p>
      )}
    </fieldset>
  );
}

// ---------- 02 Cases ----------

function Cases() {
  const { state, dispatch } = useEvalSet();
  return (
    <div data-part="cases" data-actor="shared">
      <h3>
        02 · Cases <small>({state.cases.length})</small>
      </h3>
      <table>
        <thead>
          <tr>
            <th>Input</th>
            <th>Good answer</th>
            <th>From</th>
            <th>Remove</th>
          </tr>
        </thead>
        <tbody>
          {state.cases.map((c) => (
            <tr key={c.id} data-case-source={c.source}>
              <td>{c.input}</td>
              <td>{c.ideal ?? <em data-empty>not yet known</em>}</td>
              <td>{c.source}</td>
              <td>
                <button type="button" onClick={() => dispatch({ type: 'removeCase', id: c.id })}>
                  remove
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

// ---------- 03 Rubric + 04 Pass and fail examples ----------

function newRuleId(): string {
  return typeof crypto !== 'undefined' && 'randomUUID' in crypto
    ? crypto.randomUUID()
    : `r-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
}

function Rubric() {
  const { state, dispatch, unsharedRules } = useEvalSet();
  return (
    <div data-part="rubric" data-actor="human">
      <h3>03 · Rubric</h3>
      {unsharedRules.length > 0 && (
        <p role="status" data-warning="unshared-rules">
          {unsharedRules.length} rule{unsharedRules.length === 1 ? '' : 's'} without a pass and a fail example — a
          preference, not yet a standard.
        </p>
      )}
      <ol>
        {state.rubric.map((r) => (
          <li key={r.id} data-rule-shared={Boolean(r.pass && r.fail)}>
            <textarea
              aria-label="Rule"
              value={r.text}
              onChange={(e) => dispatch({ type: 'updateRule', id: r.id, patch: { text: e.target.value } })}
            />
            <PassFail rule={r} />
          </li>
        ))}
      </ol>
      <button type="button" onClick={() => dispatch({ type: 'addRule', rule: { id: newRuleId(), text: '' } })}>
        Add rule
      </button>
    </div>
  );
}

function PassFail({ rule }: { rule: RubricRule }) {
  const { dispatch } = useEvalSet();
  return (
    <div data-part="pass-fail" data-actor="human">
      <label>
        <span data-verdict="pass">Passes</span>
        <textarea
          value={rule.pass ?? ''}
          placeholder="One answer that passes this rule"
          onChange={(e) => dispatch({ type: 'updateRule', id: rule.id, patch: { pass: e.target.value } })}
        />
      </label>
      <label>
        <span data-verdict="fail">Fails</span>
        <textarea
          value={rule.fail ?? ''}
          placeholder="One answer that fails this rule"
          onChange={(e) => dispatch({ type: 'updateRule', id: rule.id, patch: { fail: e.target.value } })}
        />
      </label>
    </div>
  );
}

// ---------- 05 Version ----------

function Version() {
  const { state } = useEvalSet();
  const { set, rubric, prompt } = state.version;
  return (
    <dl data-part="version" data-actor="interface">
      <dt>Set</dt>
      <dd>{set}</dd>
      <dt>Rubric</dt>
      <dd>{rubric}</dd>
      <dt>Prompt</dt>
      <dd>{prompt}</dd>
    </dl>
  );
}

// ---------- export ----------

export const EvalSetAuthoring = Object.assign(Root, {
  Root,
  Source,
  Cases,
  Rubric,
  PassFail,
  Version,
});

Give this to your coding agent (shadcn MCP: npx shadcn@latest mcp init --client claude):

Add the @agentsandhumans registry to components.json:
{
  "registries": {
    "@agentsandhumans": "https://agentsandhumans.ai/api/registry/{name}.json"
  }
}
Then install eval-set-authoring (npx shadcn@latest add @agentsandhumans/eval-set-authoring) and wire it into the flow where your agent turning "i know it when i see it" into examples and a standard a test can use.

Field Notes

OCT 19 · Paper · via arXiv Case DrilldownEval Set AuthoringObservability

Annotrace gives agent trace labelling a repeatable standard

The reviewer's judgement has been the bottleneck. Annotrace makes it repeatable. That changes whether a team can trust its own scores across releases, not within one sitting.

MAY 8 · Paper · via arXiv Case DrilldownEval Set Authoring

A paper gives reviewers the number of labels a judge needs

This is the number behind "calibrate with a few labels". A product can tell the reviewer how many cases to label before the judge can be trusted. Not all of them, and not none.

NOV 24 · Clipped · via Braintrust blog Production DriftEval Set Authoring

Braintrust turns a described failure into test cases and a scorer

Writing the test set was the chore that kept teams iterating by feel. Describing the failure in a sentence and getting rows and a scorer back is the step that turns the chore into a habit.

JUL 29 · Clipped · via LangChain blog Case DrilldownEval Set Authoring

LangSmith scores its judge against the team's own grades

The alignment score is the trust signal. A judge version with a number beside it, measured against the team's own grades, is something a product manager can decide to rely on.

JUN 13 · Clipped · via Google Cloud blog Comparison ViewEval Set Authoring

Google writes the rubric per example and checks each rule pass or fail

A rubric per example hides a cost: a standard that shifts with each case is hard for a team to share or defend. The pass and fail examples beside each rule are what let a team hold the rubric steady across cases.

Documented sightings (5) — anatomy-mapped observations from vendor materials
Anthropic

Cases come from paste, a spreadsheet import, or generation by Claude; an ideal-answer column stands in for a rubric.

Anthropic · Jul 10, 2024
OpenAI

Datasets are built from production traces; criteria are written in words and applied by a grader.

OpenAI · Oct 6, 2025
Braintrust

Loop turns "the model keeps apologizing twice" into dataset rows or a scorer from the logs behind it.

Braintrust docs · Sep 11, 2026
LangChain (LangSmith)

The rubric is authored as a judge prompt and tested against human-graded examples until the alignment score is acceptable.

LangChain blog · Jul 29, 2025
Google

The rubric is written per example rather than per set, and each rule is checked pass or fail with a reason.

Google Cloud blog · Jun 13, 2025

Side by Side

Anatomy part AnthropicOpenAIBraintrustLangChain (LangSmith)Google
Source manual, CSV import, or generated by Claudeproduction tracesproduction logs via Looptraces, annotation queuesdatasets
Cases rows in the Evaluate tabdataset rowsdataset rowsdataset examplesexamples
Rubric an ideal-output column, no written ruleswritten grading criteriaa scorer written from a plain descriptionjudge promptgenerated per example
Pass and fail examples ———human-graded examplespass/fail per rule
Version prompt versions onlydataset versionsdataset versionsjudge versions with a saved alignment baseline—

Convergence

Is the industry agreeing on this pattern?
0255075100JULOCT 25100%

SHARED ANATOMY, %

Similarity = Jaccard overlap of evidenced anatomy sets, Anthropic vs OpenAI, cumulative by month · n = 2 examples. Dashed spans: n < 3 — not enough evidence yet.

View as table
MonthShared anatomy %Evidence rows
2024-0701
2025-101001

Tensions & Failure Modes

  • Rot: a test set written for last quarter's product tests last quarter's behavior. Sets need a way to age out cases and add new ones from production.
  • One person's taste: a rubric only its author can apply is a preference, not a standard. Pass and fail examples beside each rule make it shared.
  • Synthetic drift: generated cases are cheap and plentiful, and they can miss how real humans phrase things. Real traffic keeps the set honest.

What we are learning from the community

Readers who build with Eval Set Authoring add what they found: questions, use cases, critiques, other ways to solve it. An editor reads each one before it shows here. Nothing here changes the pattern's status on its own.

This pattern has no contributions yet. The form below sends the first one.

Add to this pattern

A post, a comment, a thread or a repo. Leave it empty to write it here.

Or the name you post under. It shows beside your contribution.

We store a one-way hash of it, not the address.

Up to 2,000 characters. Leave it empty if the link says it all.

The Story So Far

  1. Oct 19, 2026 drift: quiet → too-early (30d sightings 0 → 1)
  2. Sep 19, 2026 Primitive published: a headless core generated from the five-part anatomy (source, cases, rubric with a pass and a fail example per rule, version), the three failure modes as states, and a ten-case promise-extraction set as the worked example. Unstyled while the pattern is watching.
  3. Sep 11, 2026 Added to the Watching list from the prompt-evals brief and the 2026-09-11 vendor pass; the Chromatic review loop shapes the comparison and release entries.

Related Patterns