\n \n \n \n )\n}\n```\n\nContext: the three functions call independent internal services; none needs another function's result. Preserve the rendered content and error semantics.","source":{"urls":["https://nextjs.org/docs/app/getting-started/fetching-data"]},"graders":[{"type":"response_not_empty"},{"type":"rubric","dimensions":[{"name":"waterfall_diagnosis","levels":[{"name":"not_met","description":"Misses the sequential request waterfall or incorrectly treats the requests as dependent."},{"name":"met","description":"Correctly identifies that serial awaits add avoidable latency because the three requests are independent, and prioritizes this as the main performance issue."}]},{"name":"concrete_parallelization","levels":[{"name":"not_met","description":"Offers only general advice or a replacement that still starts the requests sequentially or changes behavior."},{"name":"met","description":"Shows a valid replacement that initiates the independent work together, such as Promise.all over the three calls, while preserving the rendered data and failure behavior."}]},{"name":"impact_explanation","levels":[{"name":"not_met","description":"Does not connect the change to request latency or makes unsupported claims about caching or bundle size."},{"name":"met","description":"Explains that total blocking time approaches the slowest concurrent request instead of the sum of three serial waits, without inventing unprovided measurements."}]}]}]}
+{"id":"search-effect-race","prompt":"Review this Client Component from a Next.js App Router application. Users report that quickly typing and then navigating Back can sometimes show results for an older query. Return a prioritized code-review response with the smallest safe correction and any relevant architectural alternative. Do not edit files.\n\n```tsx\n'use client'\n\nimport { useEffect, useState } from 'react'\n\ntype Result = { id: string; title: string }\n\nexport function SearchResults({ query }: { query: string }) {\n const [results, setResults] = useState([])\n const [loading, setLoading] = useState(false)\n\n useEffect(() => {\n setLoading(true)\n fetch(`/api/search?q=${encodeURIComponent(query)}`)\n .then((response) => response.json())\n .then((data) => {\n setResults(data.results)\n setLoading(false)\n })\n }, [query])\n\n if (loading) return
Searching…
\n return
{results.map((result) =>
{result.title}
)}
\n}\n```\n\nContext: requests are cacheable GETs, the URL is the source of truth for `query`, and changing the API is out of scope.","source":{"urls":["https://react.dev/reference/react/useEffect","https://react.dev/learn/you-might-not-need-an-effect","https://nextjs.org/docs/app/getting-started/fetching-data"]},"graders":[{"type":"response_not_empty"},{"type":"rubric","dimensions":[{"name":"race_and_lifecycle","levels":[{"name":"not_met","description":"Misses the stale-response race and unmount lifecycle risk, or attributes the behavior only to rendering speed."},{"name":"met","description":"Explains that an earlier request can resolve after a later one or after unmount and overwrite current state, and treats request cleanup or stale-result suppression as the immediate correctness fix."}]},{"name":"safe_local_fix","levels":[{"name":"not_met","description":"Proposes a debounce alone, omits cleanup, or provides a fix that can still commit stale results or leave loading stuck."},{"name":"met","description":"Provides a coherent Effect implementation using AbortController or an ignore flag with cleanup, error or abort handling, and loading state that belongs to the active request."}]},{"name":"next_architecture_judgment","levels":[{"name":"not_met","description":"Mandates a rewrite without considering the stated URL-driven context, or gives no relevant alternative."},{"name":"met","description":"Notes that App Router server data fetching, streaming, or a client data library can provide lifecycle and caching benefits, while clearly separating that optional architecture choice from the minimal local correction."}]}]}]}
+{"id":"derived-cart-total-effect","prompt":"Perform a React performance and correctness review of this cart summary. The parent may replace `items` after applying a coupon. Return only actionable findings, ordered by impact, with corrected code where appropriate. Do not edit files.\n\n```tsx\n'use client'\n\nimport { useEffect, useState } from 'react'\n\ntype Item = { id: string; price: number; quantity: number }\n\nexport function CartSummary({ items }: { items: Item[] }) {\n const [subtotal, setSubtotal] = useState(0)\n const [total, setTotal] = useState(0)\n\n useEffect(() => {\n setSubtotal(items.reduce((sum, item) => sum + item.price * item.quantity, 0))\n }, [items])\n\n useEffect(() => {\n setTotal(subtotal * 1.0825)\n }, [subtotal])\n\n return
Total: ${total.toFixed(2)}
\n}\n```\n\nContext: the item list is normally under 30 entries, tax is a fixed 8.25%, and no external system needs to observe subtotal or total.","source":{"urls":["https://react.dev/learn/you-might-not-need-an-effect","https://react.dev/reference/react/useMemo"]},"graders":[{"type":"response_not_empty"},{"type":"rubric","dimensions":[{"name":"derived_state_diagnosis","levels":[{"name":"not_met","description":"Treats the Effect chain as necessary or focuses only on dependency-array syntax."},{"name":"met","description":"Identifies subtotal and total as values derivable during render and explains that storing them in state creates extra render passes and transient stale output."}]},{"name":"minimal_refactor","levels":[{"name":"not_met","description":"Adds more state, Effects, or unconditional memoization, or fails to preserve the calculation."},{"name":"met","description":"Shows the values calculated directly from items during render, preserving the subtotal and 8.25 percent tax computation without Effects or redundant state."}]},{"name":"memoization_judgment","levels":[{"name":"not_met","description":"Claims useMemo is required for correctness or recommends it without regard to the small stated workload."},{"name":"met","description":"Treats useMemo as optional and measurement-driven for this small list, while allowing it if profiling later shows the reduction is expensive or item identity is stable enough to benefit."}]}]}]}
+{"id":"resize-listener-subscription","prompt":"Review this React component after a report that window resizing becomes increasingly sluggish when users switch between compact and expanded modes. Provide a concise diagnosis and corrected implementation. Do not edit files.\n\n```tsx\n'use client'\n\nimport { useEffect, useState } from 'react'\n\nexport function ViewportLabel({ compact }: { compact: boolean }) {\n const [width, setWidth] = useState(0)\n\n useEffect(() => {\n function handleResize() {\n setWidth(window.innerWidth)\n analytics.track('viewport_resize', { compact, width: window.innerWidth })\n }\n\n window.addEventListener('resize', handleResize)\n handleResize()\n }, [compact])\n\n return {compact ? 'Compact' : 'Expanded'} at {width}px\n}\n```\n\nContext: `analytics.track` is a stable imported singleton, tracking should use the current `compact` value, and the component can mount and unmount repeatedly.","source":{"urls":["https://react.dev/reference/react/useEffect","https://react.dev/learn/synchronizing-with-effects"]},"graders":[{"type":"response_not_empty"},{"type":"rubric","dimensions":[{"name":"subscription_leak","levels":[{"name":"not_met","description":"Misses that listeners accumulate or incorrectly blames React Strict Mode as the production cause."},{"name":"met","description":"Identifies the missing removeEventListener cleanup as the cause of accumulating handlers across dependency changes and unmounts."}]},{"name":"correct_effect_lifecycle","levels":[{"name":"not_met","description":"Provides cleanup with a different function identity, omits compact from synchronization, or does not initialize width."},{"name":"met","description":"Shows an Effect that registers one handler, invokes it for initial synchronization, removes that same handler in cleanup, and continues to track the current compact value."}]},{"name":"strict_mode_reasoning","levels":[{"name":"not_met","description":"Recommends suppressing duplicate development execution or removing dependencies to hide the symptom."},{"name":"met","description":"Explains that React's development setup-cleanup cycle exposes lifecycle bugs and that symmetric cleanup makes the component safe rather than requiring suppression."}]}]}]}
+{"id":"hydration-personalized-header","prompt":"A Next.js App Router page intermittently logs a hydration mismatch, especially for signed-in users. Review the component and propose the smallest robust design that keeps the first server and client render consistent. Do not edit files. Include a replacement snippet and explain any user-experience tradeoff.\n\n```tsx\n'use client'\n\nexport function HeaderGreeting() {\n const name = typeof window === 'undefined'\n ? 'Guest'\n : window.localStorage.getItem('displayName') ?? 'Guest'\n const generatedAt = new Date().toLocaleTimeString()\n\n return (\n \n Hello, {name}\n Rendered at {generatedAt}\n \n )\n}\n```\n\nContext: the server does not have the display name, the timestamp is decorative, and disabling server rendering for the entire page is not acceptable.","source":{"urls":["https://nextjs.org/docs/messages/react-hydration-error","https://nextjs.org/docs/app/getting-started/server-and-client-components","https://react.dev/reference/react/useEffect"]},"graders":[{"type":"response_not_empty"},{"type":"rubric","dimensions":[{"name":"mismatch_causes","levels":[{"name":"not_met","description":"Finds only one nondeterministic value or suggests the mismatch is caused by use client itself."},{"name":"met","description":"Identifies both the localStorage or window-dependent first render and the time-dependent Date output as values that can differ between server HTML and the first client render."}]},{"name":"consistent_initial_render","levels":[{"name":"not_met","description":"Reads browser state during initial render, disables SSR for the whole page, or relies on suppressHydrationWarning as the primary blanket fix."},{"name":"met","description":"Provides a design with deterministic server and initial client markup, then reads localStorage after hydration in an Effect or passes server-known data as props, while isolating or deferring the decorative timestamp."}]},{"name":"tradeoff_and_scope","levels":[{"name":"not_met","description":"Does not mention the temporary fallback or visual transition, or recommends broad client-only rendering without justification."},{"name":"met","description":"Explains the brief Guest or placeholder state and limits any client-only or warning-suppression escape hatch to the smallest inherently nondeterministic element."}]}]}]}
+{"id":"client-boundary-product-page","prompt":"Review the module boundaries in this Next.js App Router product page. The team wants to reduce shipped JavaScript without losing the Add to cart interaction. Return a concrete refactor sketch and explain which modules execute on the server versus the client. Do not edit files.\n\n```tsx\n// app/products/[id]/page.tsx\n'use client'\n\nimport { useState } from 'react'\nimport { marked } from 'marked'\nimport { getProduct } from '@/lib/products'\nimport { SiteFooter } from '@/components/site-footer'\n\nexport default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {\n const { id } = await params\n const product = await getProduct(id)\n const [quantity, setQuantity] = useState(1)\n\n return (\n <>\n \n
{product.name}
\n \n \n \n \n \n >\n )\n}\n```\n\nContext: `getProduct` accesses a server-only database module, `marked` is needed only to render stored product copy, and `SiteFooter` is static.","source":{"urls":["https://nextjs.org/docs/app/getting-started/server-and-client-components"]},"graders":[{"type":"response_not_empty"},{"type":"rubric","dimensions":[{"name":"boundary_diagnosis","levels":[{"name":"not_met","description":"Leaves the whole page in the client graph or suggests client-side database access."},{"name":"met","description":"Explains that the use client directive creates a client boundary for imports and descendants, conflicting with server-only data access and unnecessarily pulling static or rendering work toward the client bundle."}]},{"name":"server_client_split","levels":[{"name":"not_met","description":"The proposed split loses interactivity, passes non-serializable server values, or keeps marked and the footer behind the client boundary."},{"name":"met","description":"Sketches an async Server Component page that fetches and renders product content and the static footer, plus a small Client Component island that owns quantity state and click handlers using serializable props such as productId."}]},{"name":"performance_outcome","levels":[{"name":"not_met","description":"Makes only correctness claims or promises exact bundle savings without evidence."},{"name":"met","description":"Connects the narrower client boundary to less JavaScript shipped and hydrated while preserving server-side data access and the required cart interaction, without inventing measurements."}]}]}]}
+{"id":"lazy-admin-chart","prompt":"Review this Next.js Client Component for initial-load performance. Most users never open the analytics panel, and field data shows elevated JavaScript execution time on the route. Return a prioritized recommendation with an implementation sketch and a measurement plan. Do not edit files.\n\n```tsx\n'use client'\n\nimport { useState } from 'react'\nimport { AnalyticsChart } from '@/components/analytics-chart'\nimport { buildChartSeries } from '@/lib/chart-series'\n\nexport function AdminToolbar({ rows }: { rows: ReportRow[] }) {\n const [open, setOpen] = useState(false)\n const series = buildChartSeries(rows)\n\n return (\n \n \n {open ? : null}\n \n )\n}\n```\n\nContext: `AnalyticsChart` imports a large browser-only charting library, `buildChartSeries` is expensive for large reports, and the panel is not needed for indexing or the initial view.","source":{"urls":["https://nextjs.org/docs/app/guides/lazy-loading","https://nextjs.org/docs/app/guides/production-checklist","https://react.dev/reference/react/useMemo"]},"graders":[{"type":"response_not_empty"},{"type":"rubric","dimensions":[{"name":"defer_code_and_work","levels":[{"name":"not_met","description":"Only conditionally renders the already statically imported chart or memoizes the eager import, leaving its code in the initial client graph."},{"name":"met","description":"Recommends a dynamic import for the browser-only chart and defers buildChartSeries until the panel is opened, so both download and expensive computation are avoided on the common closed path."}]},{"name":"implementation_quality","levels":[{"name":"not_met","description":"Uses an invalid dynamic import pattern, performs expensive work on every closed render, or ignores an appropriate loading state."},{"name":"met","description":"Provides a plausible next/dynamic or React lazy implementation with a loading fallback and computes the series only for the open panel, optionally memoizing it when open if repeated renders justify that."}]},{"name":"measurement_plan","levels":[{"name":"not_met","description":"Claims improvement without proposing production-oriented verification."},{"name":"met","description":"Proposes comparing production bundle analysis and route performance before and after, including the client import chain or bundle contribution and an execution or user-centric metric."}]}]}]}
+{"id":"responsive-hero-image","prompt":"Review this above-the-fold hero in a Next.js App Router page for loading performance and layout stability. Return the highest-impact findings, corrected code, and what you would verify after the change. Do not edit files.\n\n```tsx\nexport function Hero() {\n return (\n \n \n
Plan the launch with confidence
\n \n )\n}\n```\n\n```css\n.heroImage {\n display: block;\n width: 100%;\n height: auto;\n}\n```\n\nContext: the source image is 2400 by 1350 pixels, it is the route's likely largest-contentful-paint element, and it spans the viewport up to a 1200-pixel content maximum.","source":{"urls":["https://nextjs.org/docs/app/getting-started/images","https://nextjs.org/docs/app/api-reference/components/image"]},"graders":[{"type":"response_not_empty"},{"type":"rubric","dimensions":[{"name":"image_optimization","levels":[{"name":"not_met","description":"Keeps an unqualified raw img or recommends only compressing the source with no responsive delivery strategy."},{"name":"met","description":"Recommends Next.js Image with intrinsic dimensions or a correctly constrained fill container, an accurate sizes value for the responsive layout, and appropriate priority or preload treatment for the likely LCP image."}]},{"name":"layout_and_bandwidth","levels":[{"name":"not_met","description":"Omits dimensions and layout reservation or asserts that CSS width alone prevents layout shift and oversized delivery."},{"name":"met","description":"Explains how intrinsic dimensions preserve aspect ratio and reserve space, and how responsive image selection avoids sending the 2400-pixel source unnecessarily to smaller viewports."}]},{"name":"verification","levels":[{"name":"not_met","description":"Provides no verification or relies only on development-mode impressions."},{"name":"met","description":"Calls for a production-like check of LCP, layout shift, and the selected image resource or transfer size across representative viewport widths."}]}]}]}
+{"id":"negative-parallel-server-page","prompt":"Perform a performance-focused review of this Next.js App Router page. The team has no reported regression; this is a pre-merge review. State whether a code change is justified from the supplied evidence, identify any confirmed issue, and list any measurement you would request before suggesting speculative optimization. Do not edit files.\n\n```tsx\nimport { Suspense } from 'react'\nimport { getProduct, getReviews } from '@/lib/data'\n\nexport default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {\n const { id } = await params\n const productPromise = getProduct(id)\n const reviewsPromise = getReviews(id)\n const [product, reviews] = await Promise.all([productPromise, reviewsPromise])\n\n return (\n \n
{product.name}
\n Loading reviews…
}>\n \n \n \n )\n}\n```\n\nContext: both data functions are server-only and independent, the arrays are serializable, production traces and bundle reports were not provided, and `ReviewList` is synchronous once it receives `reviews`.","source":{"urls":["https://nextjs.org/docs/app/getting-started/fetching-data","https://nextjs.org/docs/app/getting-started/server-and-client-components"]},"graders":[{"type":"response_not_empty"},{"type":"rubric","dimensions":[{"name":"restraint","levels":[{"name":"not_met","description":"Invents a confirmed performance defect, mandates memoization or client fetching, or claims a Suspense streaming benefit that the already-resolved reviews prop cannot provide."},{"name":"met","description":"States that the independent fetches are already started in parallel and that no performance change is justified by the supplied evidence; it may note that the current Suspense boundary does not stream the already-awaited reviews."}]},{"name":"evidence_request","levels":[{"name":"not_met","description":"Requests vague optimization work or proposes changes before identifying a measured bottleneck."},{"name":"met","description":"Requests targeted production evidence such as server timing for each fetch, route latency, bundle analysis, or a trace before recommending further optimization."}]},{"name":"safe_optional_observation","levels":[{"name":"not_met","description":"Treats an optional restructuring as required or changes data ownership without a demonstrated need."},{"name":"met","description":"If discussing streaming, clearly labels it optional and explains that the reviews await would need to move behind an async component or promise-consuming boundary for the fallback to become meaningful."}]}]}]}
+{"id":"negative-trivial-memoization","prompt":"A reviewer proposed wrapping every component in `memo` and every calculation in `useMemo`. Assess that proposal for this component and return a go/no-go recommendation. Explain what evidence would change your decision and mention any actual correctness concern visible in the snippet. Do not edit files.\n\n```tsx\n'use client'\n\nimport { useState } from 'react'\n\nexport function GreetingCard({ name }: { name: string }) {\n const [expanded, setExpanded] = useState(false)\n const greeting = `Hello, ${name}!`\n\n return (\n \n
{greeting}
\n \n {expanded ?
Thanks for visiting.
: null}\n \n )\n}\n```\n\nContext: no lag or excessive render count has been observed, the parent behavior is not shown, and the application may enable React Compiler later.","source":{"urls":["https://react.dev/reference/react/memo","https://react.dev/reference/react/useMemo","https://react.dev/reference/react-compiler/directives/use-memo"]},"graders":[{"type":"response_not_empty"},{"type":"rubric","dimensions":[{"name":"no_go_judgment","levels":[{"name":"not_met","description":"Recommends blanket memo or useMemo, or presents memoization as required for correctness."},{"name":"met","description":"Rejects the blanket proposal for this trivial component because no expensive repeated work or lag is established and treats memoization as a performance optimization rather than a semantic requirement."}]},{"name":"evidence_threshold","levels":[{"name":"not_met","description":"Offers no condition under which memoization would be warranted or relies on intuition alone."},{"name":"met","description":"Would reconsider after production profiling shows frequent costly renders with stable props or expensive recalculation, and notes that parent prop stability and compiler configuration affect the decision."}]},{"name":"correctness_scope","levels":[{"name":"not_met","description":"Invents a correctness bug or proposes unrelated refactors."},{"name":"met","description":"States that no correctness defect is visible in the supplied snippet and preserves the current implementation absent contrary evidence."}]}]}]}
From f425a7ea503a3423e9cf3f97c576a82ca4eabf3e Mon Sep 17 00:00:00 2001
From: jon-devlapaz
Date: Sun, 16 Aug 2026 18:11:41 -0500
Subject: [PATCH 3/3] docs: define promotion evidence workflow
---
README.md | 76 +++++--
docs/minimum-eval-contract.md | 98 ++++++---
skills/skill-eval-loop/SKILL.md | 75 +++++--
.../references/eval-authoring.md | 17 +-
tasks/plan.md | 195 +++++++++++-------
tasks/todo.md | 13 +-
6 files changed, 331 insertions(+), 143 deletions(-)
diff --git a/README.md b/README.md
index 04eefe8..352e5de 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,10 @@
# skill-eval-loop
`skill-eval-loop` is a self-contained Python 3 Agent Skill that measures
-whether access to one local skill changes task outcomes. It runs the same task
-under a no-skill control and an exact-hash treatment, then retains the raw
-evidence and a comparison report.
+whether explicitly applying one local skill changes task outcomes. The control
+receives the original task. The treatment receives the exact hashed skill's
+`SKILL.md` instructions in its prompt, with the installed payload available for
+referenced files. The runner retains the raw evidence and a comparison report.
## Install
@@ -49,30 +50,59 @@ Run a side-effect-free plan before a live invocation:
Verify the printed hashes and invocation counts, obtain authorization for the
live calls, then run the same command without `--dry-run`.
-For rubric tasks, also pass `--judge-model` with a different exact model
-identifier. The runner judges each condition only after deterministic gates
-pass. A valid same-provider judgment is `provisional_non_independent`; a
-timeout, failed gate, malformed response, or identity mismatch is `unknown`.
-A missing trace-reported model is unattested, not a quality unknown.
+### Public reference benchmark
+
+The checked-in development benchmark evaluates Vercel's
+`vercel-react-best-practices` skill against the no-skill control:
+
+- repository: `https://github.com/vercel-labs/agent-skills.git`
+- revision: `b8caa260a420a73042e35521de4b5c8baf6446cc`
+- skill path: `skills/react-best-practices`
+- tasks: `tasks/react-best-practices-v1.jsonl`
+- expected evaluator payload SHA-256:
+ `5cbdbd8d9acc6913b8f4e0c7151830e88417872421a5975b86fa4b3eba5c36d3`
+- expected task SHA-256:
+ `621a609cfcdb82756ebe6870a0fad16c6ef12f6186f6c75abb213195b4333c92`
+
+Fetch that exact revision into a controlled local directory and pass the
+absolute skill subpath plus the checked-in task file to `run --dry-run`. Reject
+the plan if the revision or payload hash differs. The public task file is
+development evidence, not a secret client holdout.
-The runner invokes Codex sequentially in read-only mode. Odd trials run
-control first; even trials run treatment first. It retains `run.json`, the
+For rubric tasks, also pass `--judge-model` with a different exact model
+identifier and `--calibration /absolute/path/to/calibration.json` from an
+accepted calibrate run. The runner judges each condition only after
+deterministic gates pass. A valid same-provider judgment is
+`provisional_non_independent`; a timeout, failed gate, malformed response, or
+identity mismatch is `unknown`. A missing trace-reported model is unattested,
+not a quality unknown. Omitting `--calibration` is allowed, but a rubric run
+then remains quality-incomplete and cannot exit `0`.
+
+The runner invokes Codex sequentially in read-only mode, emitting invocation
+progress to stderr. Odd trials run control first; even trials run treatment
+first. The evaluator injects the exact `SKILL.md` text itself, so treatment
+exposure does not depend on model-side discovery. Target, judge, and calibration
+invocations share one lifecycle that uses cleaned OS-temporary workspaces outside
+the evaluator repository. It retains `run.json`, the
planned configuration, tasks, condition responses, traces, stderr, and a
JSON/Markdown report for every pair.
-`runner_valid` means the runner held its declared variables and isolation
-checks. It is not a general quality claim. Read both transcripts before
+`runner_valid` means the runner held its declared variables, isolation checks,
+and treatment activation. It is not a general quality claim. Read both transcripts before
interpreting `treatment_only`, `both_pass`, `control_only`, or `both_fail`.
-JSON and Markdown reports also expose activation (currently unknown),
-calibration (`not_run`), every judged dimension, `quality_status`, and
+JSON and Markdown reports expose evaluator-recorded instruction delivery plus
+optional trace telemetry when Codex also reads the installed skill, rolled-up timing and token usage,
+calibration (`not_run`, or `accepted` plus `fixtures_sha256` when a bound
+calibration is supplied), every judged dimension, `quality_status`, and
`quality_outcome`. Deterministic-only reports say semantic quality was not
judged. An overall pairwise winner is not a quality pass when any dimension is
-unknown or disagrees with that winner.
+unknown or favors the opposing condition. A tied dimension is compatible with
+an otherwise coherent winner.
-Live exit status is `0` when quality evidence is complete, `1` when the runner
-is valid but quality is unknown or was not judged, and `2` when the runner is
-invalid.
+Live exit status is `0` when quality evidence is complete, which for rubric
+runs requires a bound accepted calibration, `1` when the runner is valid but
+quality is unknown or was not judged, and `2` when the runner is invalid.
Calibrate the pairwise judge against versioned human-labeled
`known-better`, `known-worse`, and `tie` cases before a live quality pilot:
@@ -98,8 +128,18 @@ same-provider rubric judge, blinded pairwise comparison, and human-labeled
calibration fixtures. It does not provide independent judging, pricing,
parallel execution, provider discovery, or adapters for other harnesses.
+Live evaluation is a trusted local-operator workflow. The configured harness
+and Codex executable can read the run-local Codex credentials and therefore
+must be trusted. This project does not sandbox hostile executables. Keep raw
+run directories local and inspect them before sharing any evidence.
+
## Development
+Pull-request and push CI verifies evaluator mechanics with deterministic tests
+and fake harnesses. It makes no live model calls, receives no model credentials,
+and uploads no evaluation evidence. Authorized operators run live evaluations
+locally; humans inspect the retained evidence and own promotion decisions.
+
Run the Python test suite and package healthcheck:
```bash
diff --git a/docs/minimum-eval-contract.md b/docs/minimum-eval-contract.md
index 2b70bf7..a14f8f7 100644
--- a/docs/minimum-eval-contract.md
+++ b/docs/minimum-eval-contract.md
@@ -48,7 +48,7 @@ Each non-empty JSONL line is one task:
Required fields are:
-- `id`: a unique, non-empty string;
+- `id`: a unique, non-empty, path-safe string;
- `prompt`: a non-empty string;
- `graders`: a non-empty list of supported graders.
@@ -92,29 +92,57 @@ This is a suite-bootstrap mechanism, not proof that tasks represent real use.
Use independently sourced task data, blinded judging, and human calibration for
skill-quality claims.
+## Development versus promotion
+
+Repository pull-request and push CI verifies evaluator mechanics with
+deterministic tests and fake harnesses only. It makes no live model calls,
+receives no model credentials, and publishes no raw evaluation evidence.
+Authorized operators run live development and promotion evaluations locally.
+Humans inspect the retained evidence and own the promotion decision.
+
+The default `run` role is `development`. Development suites may be visible to
+the skill author and optimization loop. They are useful for debugging and
+regression detection, but repeated hill-climbing turns them into training data.
+
+`run --promotion` is a stricter execution guardrail. It requires:
+
+- an explicit `--tasks` path controlled outside the target skill;
+- at least three trials;
+- accepted calibration when the task set contains a rubric.
+
+The retained configuration records `evaluation_role` as `development` or
+`promotion`. The flag cannot prove that a task set was independently authored,
+kept hidden, representative of real use, or labeled by humans. Those remain
+operator evidence requirements. A second model or provider does not replace a
+human-labeled holdout.
+
## Paired execution
Every task trial runs twice:
-- `control`: the target skill is unavailable;
-- `treatment`: the exact hashed skill payload is available.
+- `control`: the target skill is unavailable and Codex receives the original
+ task prompt;
+- `treatment`: the exact hashed skill payload is available and the evaluator
+ injects its exact `SKILL.md` text before the original task prompt.
-Prompt, harness, model, timeout, fixture, and tool posture remain fixed. Runs
-are sequential. Condition order alternates by trial to reduce a fixed-order
-confound. Trials never retry silently.
+The original task, harness, model, timeout, fixture, and tool posture remain
+fixed. The instruction injection is part of the treatment. Runs are sequential.
+Condition order alternates by trial to reduce a fixed-order confound. Trials
+never retry silently. The CLI emits invocation progress to stderr and stops
+before repeating a detected network or transport failure.
-Each condition starts in an empty read-only workspace. The minimum runner does
-not seed a repository or fixture tree. Consequently, repository-editing tasks
+Each condition starts in an empty OS-temporary read-only workspace outside the
+evaluator repository, preventing ancestor project instructions from entering
+the trial. The minimum runner does not seed a repository or fixture tree. Consequently, repository-editing tasks
and claims about executed project tests are not reproducible under this
contract; use self-contained response tasks until a separately justified
workspace-fixture capability exists.
-The intervention is availability of the exact hashed skill payload, not a
-required execution path. Trace evidence about skill access is diagnostic when
-available. Its absence does not invalidate the paired outcome comparison and
-must not be scored as output quality. Results may claim only that access to the
-skill changed measured outcomes under the retained configuration, not that the
-model definitely read or followed the skill.
+The intervention is evaluator-owned injection of the exact hashed skill's main
+instructions. This guarantees treatment exposure without depending on the
+model to discover or open `SKILL.md`; the installed payload remains available
+for referenced files. Delivery does not prove faithful compliance, so human
+transcript review remains required.
The first Codex implementation is deliberately direct. A shared harness
abstraction is not justified until a second real harness demonstrates common
@@ -122,22 +150,29 @@ behavior.
## Codex home isolation
-The experiment Codex home is not the user's `~/.codex`. A live run creates
+The experiment Codex home is not the user's `~/.codex`. A live run temporarily creates
`$output/codex-home` and sets `CODEX_HOME` to that directory for control,
treatment, and judge. Place it under the output directory, not the OS temp
directory: some Codex builds refuse a temp-dir home.
If `~/.codex/auth.json` exists, copy only that file into the run-local home.
Do not copy skills, sessions, or `config.toml`. Copied credentials are
-runtime-only. They are not retained evidence and must not appear in reports.
-Dry-run and fake-harness runs must not require an authenticated host Codex
-home.
+runtime-only and the runner removes the entire run-local home when it exits. The runner
+does not intentionally serialize credentials into evidence. Because the
+configured executable can read the copied file, the local operator must trust
+the harness and inspect raw artifacts before sharing them. Dry-run and
+fake-harness runs must not require an authenticated host Codex home.
The treatment skill remains a workspace payload at `.agents/skills/`.
Host `CODEX_HOME/skills` is not the intervention and is not consulted. A
same-name skill in the user's Codex home is not a runner gate once the
experiment uses a run-local home.
+This isolation protects the experiment from ambient Codex configuration; it is
+not a security sandbox for hostile executables. Strong isolation of untrusted
+harnesses requires a separate OS or broker boundary and is outside this
+project.
+
## Dry-run accounting
Dry-run validates consumed inputs and prints the complete plan without creating
@@ -186,7 +221,9 @@ Rubric judging begins only after both condition runs satisfy runner isolation
and execution checks and every deterministic grader passes. A failed gate
produces quality status `unknown` and makes no judge call.
-Each qualifying condition is judged separately in a fresh read-only workspace.
+Each qualifying condition is judged separately in a fresh OS-temporary read-only
+workspace outside the evaluator repository. Target, judge, and calibration
+roles share the same workspace, environment, process, trace, and cleanup lifecycle.
The prompt presents the task, untrusted candidate response, and locked rubric,
but no control or treatment label. For every dimension, the judge must return
concrete response evidence and exactly one declared level. The runner retains
@@ -237,11 +274,14 @@ content is unchanged.
## Reports and exit status
-Pair reports separate runner validity, activation, deterministic comparison,
+Pair reports separate runner validity, evaluator-recorded activation, deterministic comparison,
per-output rubric status, pairwise status, quality completeness, quality
outcome, and calibration. `run.json` repeats the rolled-up runner validity and
-quality status. Activation is `unknown` with reason `telemetry_unavailable`
-until a later telemetry source exists. Calibration is `accepted` only when a
+quality status and rolled-up timing/token usage. Activation is `observed` when
+the evaluator injects the hashed treatment instructions. `trace_skill_read`
+separately records whether Codex opened the installed main file; it is telemetry,
+not a validity gate.
+Calibration is `accepted` only when a
validated binding is supplied. Without `--calibration`, a rubric run records
`not_run`, quality remains `unknown`, and the runner cannot exit `0`.
@@ -249,8 +289,9 @@ validated binding is supplied. Without `--calibration`, a rubric run records
any required judgment is unknown, and `provisional_non_independent` when every
required judgment succeeded. `quality_outcome` lists every dimension through
`dimension_results` and is never a restored winner when a pairwise dimension
-disagrees with the overall winner (`inconsistent`) or when quality is unknown
-or not judged. Deterministic-only Markdown reports state that semantic quality
+favors the condition opposing the overall winner (`inconsistent`) or when
+quality is unknown or not judged. A tied dimension is compatible with an
+otherwise coherent winner. Deterministic-only Markdown reports state that semantic quality
was not judged.
Process exit status distinguishes those cases:
@@ -275,6 +316,15 @@ distribution, repeated trials, fair graders, and human review of transcripts.
Improvement, regression, and no difference are all legitimate outcomes of a
valid run.
+A promotion claim additionally requires an independently controlled holdout,
+human labels for the rubric or preference decisions, and measured agreement
+between those labels and any automated judge. A visible development suite must
+not be relabeled as a holdout after it has guided changes.
+
+The remaining real-promotion gate is external to this runner: independently
+control the holdout, obtain human labels, repeat trials, and review the retained
+transcripts before making a promotion claim.
+
A one-task pilot can establish runner acceptance. It cannot establish that a
skill is generally effective. Capability suites should contain enough
realistic, unsaturated tasks to reveal meaningful differences; regression
diff --git a/skills/skill-eval-loop/SKILL.md b/skills/skill-eval-loop/SKILL.md
index b73280b..f06f118 100644
--- a/skills/skill-eval-loop/SKILL.md
+++ b/skills/skill-eval-loop/SKILL.md
@@ -40,8 +40,10 @@ requirements. Use `file_exists` and `json_equal` only when the configured
harness can create the stated workspace artifact. Keep unknown task metadata
for human review; it does not affect execution.
-The current runner starts each condition in an empty read-only workspace. Use
-response-only tasks unless the prompt itself contains all required material.
+The current runner starts each target, judge, and calibration invocation in an
+empty OS-temporary read-only workspace outside the evaluator repository. Those
+roles share one process and cleanup lifecycle. Use response-only tasks unless
+the prompt itself contains all required material.
Do not use repository-editing or test-running tasks as quality evidence: there
is no seeded repository for the agent to change or verify.
@@ -98,6 +100,35 @@ OpenAI model is explicitly same-provider evidence, not an independent judgment.
A recommended OpenAI-only pair is `--model gpt-5.6-terra --judge-model
gpt-5.6-sol`.
+The default evaluation role is `development`. Treat any suite visible to the
+skill author or repeatedly used during hill-climbing as development or
+regression evidence, even when it is locked and hash-bound.
+
+For a promotion run, use an independently controlled task file, accepted
+calibration, and repeated trials:
+
+```bash
+"$EVALUATOR" run \
+ --skill /absolute/path/to/target-skill \
+ --tasks /absolute/path/to/operator-controlled-holdout.jsonl \
+ --output /absolute/path/to/fresh-promotion-run \
+ --harness codex \
+ --harness-bin /absolute/path/to/codex \
+ --model exact-model-id \
+ --judge-model exact-judge-model-id \
+ --calibration /absolute/path/to/fresh-calibration/calibration.json \
+ --trials 3 \
+ --timeout-seconds 300 \
+ --promotion \
+ --dry-run
+```
+
+`--promotion` rejects target-owned tasks, fewer than three trials, and rubric
+runs without accepted calibration. It records the promotion role; it does not
+prove task independence, representativeness, human labeling, or judge
+agreement. Retain that evidence separately and keep the holdout unavailable to
+the hill-climbing agent.
+
## Calibrate the pairwise judge
Score the judge against versioned human-labeled cases before a live quality
@@ -123,8 +154,8 @@ calibration without both orientations cannot bind a rubric run. Disagreements
keep the human rationale. Exit `0` if accepted, `1` if the runner is valid but
below threshold, and `2` if a judgment is invalid.
-For Task 8, the operator-controlled `calibration.json` and its original
-absolute fixture path are the binding trust root. The runner validates their
+The operator-controlled `calibration.json` and its original absolute fixture
+path are the binding trust root. The runner validates their
internal consistency, models, labels, agreement threshold, assignment
orientations, and fixture hash. It does not authenticate the origin of the raw
judge artifacts. Keep the calibration directory and fixture under controlled
@@ -138,11 +169,14 @@ pilot until calibration is accepted and a human reviews disagreements.
Run the identical command without `--dry-run`. The runner:
-- uses a no-skill control and an exact-hash treatment;
+- gives the control the original task and injects the exact-hash treatment's
+ `SKILL.md` instructions before that task;
- runs sequentially, alternating control-first and treatment-first by trial;
+- emits invocation progress to stderr and stops after a detected infrastructure failure;
- invokes Codex in read-only mode;
- retains response, trace, stderr, execution metadata, and reports;
-- runs deterministic gates before any rubric judge;
+- records treatment instruction delivery and requires deterministic gates before
+ any rubric judge;
- asks the judge for concrete evidence and one locked level per dimension;
- never retries silently.
@@ -163,10 +197,13 @@ quality evidence, not runner validity.
`quality_status` is evidence completeness. `quality_outcome` is `not_judged`
when there is no rubric, `unknown` when any required judgment is unknown,
`tie` when the restored winner is a tie, `inconsistent` when a pairwise
-dimension disagrees with the overall winner, or the restored winner condition.
+dimension favors the condition opposing the overall winner, or the restored
+winner condition. A tied dimension does not contradict an overall winner.
An overall winner is never a quality pass when a dimension is unknown or
-disagrees. Activation is reported as unknown because Codex telemetry is not
-scored. A bound accepted calibration records `calibration_status: accepted`
+disagrees. Evaluator-owned treatment injection is `observed`; optional trace
+telemetry records whether Codex also opened the installed `SKILL.md`. Delivery
+proves exposure, not faithful compliance.
+A bound accepted calibration records `calibration_status: accepted`
and `fixtures_sha256` in `run.json` and every pair report. Without a binding,
calibration remains `not_run` and rubric quality remains `unknown`.
@@ -183,15 +220,17 @@ contains the source hash, and any trace-reported model identity agrees with the
requested model.
A live run creates `$output/codex-home` and points Codex at that directory.
-If `~/.codex/auth.json` exists, it is copied there for the process and removed
-afterward. Do not treat that file as retained evidence. Host Codex skills are
-not part of the intervention.
-
-Treat access to the exact hashed payload as the intervention. A trace may help
-explain how Codex used that access, but missing activation telemetry does not
-invalidate the outcome comparison or become a quality score. Phrase the result
-as the measured effect of skill access under the recorded configuration; do not
-claim that Codex definitely read or followed the skill.
+If `~/.codex/auth.json` exists, it is copied there for the process. The entire
+run-local Codex home is removed afterward; it is not retained evidence. Use only a trusted
+harness: it can read the run-local credential file, and this evaluator is not
+a sandbox for hostile executables. Keep raw runs local and inspect them before
+sharing. Host Codex skills are not part of the intervention.
+
+Treat injection of the exact hashed payload's `SKILL.md` instructions as the
+intervention. The control receives the original task; the treatment receives
+those instructions before that task and can access the installed payload for
+references. Injection proves exposure, not faithful compliance, so inspect the
+response before making a quality claim.
Do not claim broad skill quality from one pilot or from same-provider judging.
Use realistic unsaturated tasks, repeated trials, deterministic outcomes,
diff --git a/skills/skill-eval-loop/references/eval-authoring.md b/skills/skill-eval-loop/references/eval-authoring.md
index a87a3f6..88d8f19 100644
--- a/skills/skill-eval-loop/references/eval-authoring.md
+++ b/skills/skill-eval-loop/references/eval-authoring.md
@@ -30,4 +30,19 @@ payload fixed after this point. Run the evaluator's dry-run to validate the
JSONL before authorizing live calls.
This boundary prevents conversational leakage, not filesystem access. Treat the
-post-authoring diff audit as required evidence.
+post-authoring diff audit as required evidence. The resulting visible suite is
+a development-suite bootstrap, not a promotion holdout.
+
+For an externally grounded public benchmark, keep the task file outside the
+target and pass it with `--tasks`. The author receives the task contract and
+allowed authoritative sources, but must not inspect the target skill, candidate
+outputs, or prior reports. Record source URLs as inert task metadata. Once the
+benchmark is checked in or used for optimization, classify it as development
+evidence even if its initial authoring was independent.
+
+Promotion tasks must be controlled independently of the skill author and the
+hill-climbing loop. Keep them outside the target skill, provide them through an
+explicit `--tasks` path, attach human labels and rationales under operator
+custody, and use `run --promotion`. Do not inspect or revise the holdout in
+response to model outputs. If the tasks become visible during optimization,
+reclassify them as development evidence and replace the holdout.
diff --git a/tasks/plan.md b/tasks/plan.md
index 04d0a24..40cee37 100644
--- a/tasks/plan.md
+++ b/tasks/plan.md
@@ -23,9 +23,9 @@ control/treatment baseline, semantic grading, multi-dimensional rubrics,
- Use the existing Codex authentication for the first semantic path, label all
OpenAI-to-OpenAI results provisional and non-independent, and do not claim
independence until a different provider or human calibration supplies it.
-- Treat availability of the exact hashed skill payload as the intervention.
- Activation telemetry is optional diagnostic evidence, not a quality score or
- a gate on the outcome comparison.
+- Treat evaluator-owned injection of the exact hashed skill's `SKILL.md` as the
+ intervention. Keep the control task untouched and the installed treatment
+ payload available for referenced files.
- Give each live run a private Codex home under `$output/codex-home`. Copy
only `~/.codex/auth.json` when present. Do not reuse the user's Codex home
as the experiment environment.
@@ -81,22 +81,23 @@ outcomes.
## Task 2: Confirm intervention semantics
**Description:** Confirm what the paired experiment changes and what it may
-claim. The intervention is access to the exact hashed skill payload. Activation
-telemetry can diagnose how Codex used that access, but is not required for an
-outcome comparison and must not become a path-based quality metric.
+claim. The intervention is injection of the exact hashed skill's main
+instructions: the control receives the original task and treatment receives
+those instructions plus access to the installed payload.
**Acceptance criteria:**
-- [x] Control absence, treatment presence, and treatment/source hash equality
- define the isolated intervention.
-- [x] Missing activation telemetry does not invalidate an outcome comparison.
-- [x] Claims are limited to the measured effect of skill access under the
- retained configuration.
+- [x] Control absence, treatment presence, treatment/source hash equality, and
+ treatment-only instruction injection define the intervention.
+- [x] Evaluator-owned delivery is recorded independently of optional skill-read
+ trace telemetry.
+- [x] Claims are limited to the measured effect of injected skill instructions
+ under the retained configuration.
**Verification:**
- [x] A controlled fixture asserts control absence, treatment presence, and
treatment/source hash equality.
-- [x] Manual check: Codex CLI 0.147.0 treatment trace exposes no activation
- event; this remains diagnostic rather than a gate.
+- [x] A fake-harness regression proves treatment delivery does not depend on a
+ model-side file read.
- [x] Human review accepted the outcome-based evidence definition.
**Dependencies:** None.
@@ -106,12 +107,13 @@ outcome comparison and must not become a path-based quality metric.
- `skills/skill-eval-loop/SKILL.md`
- `docs/minimum-eval-contract.md`
-**Result:** Resolved without activation machinery.
+**Result:** Treatment instruction delivery and payload isolation are part of
+runner validity; model-side reads are retained as optional telemetry.
### Checkpoint: Evidence contract
- [x] Tasks 1 and 2 are complete.
-- [x] Deterministic validity, exact skill availability, and quality evidence
+- [x] Deterministic validity, injected skill exposure, and quality evidence
remain separate concepts.
- [x] Human approves `gpt-5.6-sol` as the provisional judge for
`gpt-5.6-terra` runs, without an independence claim.
@@ -137,13 +139,12 @@ and raw output, and label same-provider results as non-independent.
- [x] Tests pass: `python3 -m unittest discover -s tests -v`.
- [x] Focused fake-adapter tests cover success, malformed response, timeout,
identity mismatch, and deterministic short-circuiting.
-- [ ] Manual check: inspect a retained live judge trace with the selected
+- [x] Manual check: inspect a retained live judge trace with the selected
provider after separate authorization.
**Dependencies:** Tasks 1 and 2; human approval of the provisional pairing.
-**Result:** Implementation complete with fake-adapter evidence. Live provider
-verification remains part of the later authorized pilot.
+**Result:** Implementation and authorized live-provider verification complete.
**Files likely touched:**
- `skills/skill-eval-loop/scripts/skill_eval_loop.py`
@@ -173,13 +174,14 @@ host Codex home. Land this before any authorized live Codex run.
- [x] Tests pass: `python3 -m unittest discover -s tests -v`.
- [x] Focused tests assert the subprocess `CODEX_HOME` path and that fake
runs no longer need `CODEX_HOME` in the caller environment.
-- [ ] Manual check: one authorized live exec with only copied `auth.json`
- remains deferred to the later pilot.
+- [x] Manual check: an authorized live exec used the runtime credential and
+ removed the entire run-local Codex home afterward.
**Dependencies:** Task 3.
-**Result:** Implementation complete with fake-adapter evidence. Live provider
-verification remains part of the later authorized pilot.
+**Result:** Implementation and authorized live-provider verification complete.
+Target, judge, and calibration roles now share one `CodexRuntime` process and
+cleaned OS-temporary workspace lifecycle, preventing role-specific isolation drift.
**Files likely touched:**
- `skills/skill-eval-loop/scripts/skill_eval_loop.py`
@@ -205,13 +207,12 @@ report.
**Verification:**
- [x] Tests pass: `python3 -m unittest discover -s tests -v`.
- [x] Focused tests prove condition labels cannot enter the judge payload.
-- [ ] Manual check: compare the retained blind prompt, raw judgment, and
+- [x] Manual check: compare the retained blind prompt, raw judgment, and
restored report.
**Dependencies:** Tasks 1 and 3.
-**Result:** Implementation complete with fake-adapter evidence. Live prompt and
-restored-report review remains part of the later authorized pilot.
+**Result:** Implementation and authorized live prompt/report review complete.
**Files likely touched:**
- `skills/skill-eval-loop/scripts/skill_eval_loop.py`
@@ -248,13 +249,12 @@ critical failed or unknown dimension.
**Verification:**
- [x] Tests pass: `python3 -m unittest discover -s tests -v`.
- [x] Focused report fixtures cover pass, tie, failed critical dimension,
- unavailable judge, and activation unknown.
-- [ ] Manual check: inspect JSON and Markdown reports for the same pair.
+ unavailable judge, and runner-invalid isolation failures.
+- [x] Manual check: inspect JSON and Markdown reports for the same pair.
**Dependencies:** Tasks 2, 3, and 4.
-**Result:** Implementation complete with fake-adapter evidence. Live report
-inspection remains part of the later authorized pilot.
+**Result:** Implementation and authorized live report inspection complete.
**Files likely touched:**
- `skills/skill-eval-loop/scripts/skill_eval_loop.py`
@@ -289,10 +289,11 @@ then run one real paired pilot only if calibration accepts the chosen judge.
**Result:** Calibration command and v1 fixtures are complete. Live `gpt-5.6-sol`
calibration against the locked cases accepted 3/3 with no disagreements.
Codex 0.147.0 `exec --json` traces do not report a model; missing identity is
-unattested CLI configuration, not a failed judgment. One paired live pilot
-reported runner validity, activation unknown, deterministic both_pass, per-
-dimension rubric scores, and a blinded pairwise tie. That is not a skill-
-quality claim. Judge evidence remains same-provider and non-independent.
+ unattested CLI configuration, not a failed judgment. One paired live pilot
+ under the superseded availability-only intervention reported activation
+ unknown, deterministic both_pass, per-dimension rubric scores, and a blinded
+ pairwise tie. It is retained as historical evidence, not an applied-skill
+ quality claim. Judge evidence remains same-provider and non-independent.
**Files likely touched:**
- `tests/fixtures/`
@@ -316,57 +317,66 @@ quality claim. Judge evidence remains same-provider and non-independent.
| Risk | Impact | Mitigation |
|---|---|---|
-| Codex exposes no activation telemetry | High | Stop at Task 2 and narrow the claim rather than infer use. |
+| Codex does not open the installed `SKILL.md` | Low | Inject the exact main instructions in treatment; retain file-read events only as telemetry. |
| No independent judge is available | High | Label OpenAI-only evidence provisional and require human calibration before broader claims. |
| Judge prompt leaks condition labels | High | Build prompt from anonymized candidates and test the raw payload. |
| Rubric is gamed or too vague | High | Lock it before runs and calibrate against human-labeled cases. |
| Pilot is saturated or too small | Medium | Report tie/no-signal and expand only after calibration. |
| Host Codex home leaks extra skills | High | Use a run-local `$output/codex-home` and copy only `auth.json`. |
-| Copied `auth.json` is published as evidence | High | Treat it as runtime-only; keep it out of reports and condition artifacts. |
+| A hostile harness reads or echoes `auth.json` | High | Live runs are trusted local-operator workflows; hostile-process isolation is a separate system. Keep raw artifacts local and inspect before sharing. |
## Open questions
- Which provider or human calibration process will supply independent evidence
beyond the provisional OpenAI judge?
-- What activation evidence can current Codex emit, if any?
+- Do referenced multi-file skills require an additional fixture before promotion use?
- Which human-approved threshold should calibration meet before a pilot result
is considered quality evidence?
-## Phase 2: Karpathy hill climb (next agent)
+## Phase 2: Validate one public skill
-**Baseline:** branch `python-core-redesign`, no upstream. Tasks 1–6 code and
-docs are committed. `python3 -m unittest discover -s tests -v` is green (22
-tests). Live artifacts under `.eval-runs/` are gitignored: `calibrate-v1b`
-accepted 3/3 (still `provisional_non_independent`); `pilot-v1` was a saturated
-toy (“Choose Blue.” / pairwise tie). Codex CLI 0.147.0 traces omit model
-identity; missing identity is unattested, not fail-closed.
+**Objective:** Demonstrate the evaluator on one frozen public skill versus the
+existing no-skill control. Use an independently authored, externally grounded
+benchmark; do not select a weak opponent or change the evaluator into a
+multi-skill comparison framework.
-**Objective:** Make `skill-eval-loop` a CI-gated hill climb on one locked
-non-toy skill suite: `run` consumes an accepted `calibrate` fixture hash; live
-calibration A/B-flips so `A` is not always the known-better seed; a live paired
-`calibrate` then `run` exits `0` with complete quality evidence
-(`quality_outcome` never a restored winner when a dimension is unknown or
-inconsistent). Same-provider judging stays `provisional_non_independent`. Out
-of scope: modularizing the evaluator script, installing GSD/NTT123, and any
-quality-winner claim on the toy pilot.
+**Selected target:** Vercel's `vercel-react-best-practices` skill.
-**Do not start by splitting `skills/skill-eval-loop/scripts/skill_eval_loop.py`.**
-The bottleneck is eval validity, not file size.
+- repository: `https://github.com/vercel-labs/agent-skills.git`
+- revision: `b8caa260a420a73042e35521de4b5c8baf6446cc`
+- subpath: `skills/react-best-practices`
+- evaluator payload SHA-256:
+ `5cbdbd8d9acc6913b8f4e0c7151830e88417872421a5975b86fa4b3eba5c36d3`
+- declared skill license: MIT
-**Stop and ask** if the first target skill is unnamed, if the user wants a
-second-provider judge before the CI gate, or if transcripts are still
-`human_transcript_review_required`.
+The repository does not vendor the target. An operator fetches the exact
+revision into a temporary or controlled source directory, verifies the revision,
+then passes the absolute skill subpath to the evaluator. `run.json` binds the
+actual payload hash.
-### Task 7: Lock a non-toy skill suite
+### Task 7: Lock a public benchmark
-**Description:** Replace the toy Blue prompt with one real skill directory and
-a locked JSONL suite that can fail. Do not invent the skill; ask.
+**Description:** Replace the prior skill-specific suite with response-only
+React/Next review tasks authored without inspecting the target skill. Ground
+task metadata in official React and Next.js documentation.
**Acceptance criteria:**
-- [ ] Named skill path and task file are recorded here and used by later tasks.
-- [ ] Tasks are not saturated at baseline (not every row `both_pass` by design).
-
-**Dependencies:** User names the skill. No code until that answer exists.
+- [x] The public target identity, immutable revision, subpath, license, and
+ evaluator payload hash are recorded.
+- [x] `tasks/react-best-practices-v1.jsonl` contains realistic positive,
+ negative-control, ambiguous, and false-positive-sensitive cases.
+- [x] The suite passes an evaluator dry-run against the frozen target.
+
+The checked-in suite is a public development benchmark, not a secret promotion
+holdout. A client promotion claim still requires an independently controlled
+task file that was unavailable to the skill-authoring and hill-climbing loop.
+
+**Result:** A fresh-context author created ten response-only tasks using only
+official React and Next.js documentation. The suite includes two explicit
+negative controls. Dry-run against the exact target revision is valid with
+target hash `5cbdbd8d9acc6913b8f4e0c7151830e88417872421a5975b86fa4b3eba5c36d3`,
+task hash `621a609cfcdb82756ebe6870a0fad16c6ef12f6186f6c75abb213195b4333c92`,
+10 paired trials, 50 planned invocations, zero provider calls, and no artifacts.
### Task 8: Bind calibration into live `run`
@@ -384,32 +394,63 @@ drifts.
- [x] `python3 -m unittest discover -s tests -v`
- [x] Focused tests cover hash bind, missing calibration, and A/B flip.
-**Dependencies:** Task 7 for the live suite; tests can land first.
+**Dependencies:** The calibration implementation is independent of the selected
+public target.
**Result:** Production calibration alternates both candidate orientations.
Rubric runs bind a validated accepted calibration and fixture hash; missing
calibration cannot complete quality evidence, and malformed or drifted supplied
bindings are runner-invalid. Unit and fake-harness verification is complete;
-no external Codex run was added. The Task 8 trust root is the operator-controlled
-`calibration.json` plus its original absolute fixture path. Task 9 must keep one
-stable CI path or separately approve a portable content-addressed design.
+no external Codex run was added. The trust root is the operator-controlled
+`calibration.json` plus its original absolute fixture path.
-### Task 9: CI as the product UI
+### Task 9: CI protects evaluator mechanics
-**Description:** Add a CI job that runs unit tests and, when secrets exist, the
-locked calibrate-then-run pair. The gate is complete hash-bound quality
-evidence, not a skill-quality winner.
+**Description:** Keep CI deterministic and credential-free. CI tests evaluator
+mechanics with fake harnesses; authorized operators run live evaluations
+locally. Complete hash-bound quality evidence is a local evaluation property,
+not a CI or skill-quality claim.
**Acceptance criteria:**
-- [ ] CI fails on unittest failure or runner-invalid (`exit 2`).
-- [ ] Rubric runs without bound accepted calibration cannot look like a quality
+- [x] CI fails on unittest failure or runner-invalid (`exit 2`).
+- [x] Rubric runs without bound accepted calibration cannot look like a quality
pass.
**Dependencies:** Task 8.
-### Task 10: Independent judge or holdout
+**Result:** Pull-request and push CI run the Python tests, healthcheck, packaging
+checks, and fake-harness coverage. CI has no live model invocation, model
+credential, calibration run, or raw evidence upload. Public benchmark and live
+calibration runs remain local, explicit operator actions. Real promotion
+evidence remains external and incomplete; no treatment winner is claimed.
+
+### Task 10: Human-labeled holdout and judge validation
-**Description:** Only after Tasks 8–9. A second provider or a held-out human
-set. Same-provider evidence stays provisional until then.
+**Description:** Separate visible development/regression cases from promotion
+evidence. Promotion uses an independently controlled, human-labeled holdout,
+repeated trials, and measured agreement between human labels and any automated
+judge. A second provider is useful corroboration, not a substitute for the
+holdout or human agreement.
-**Dependencies:** Tasks 8 and 9. User approval before adding a provider.
+**Acceptance criteria:**
+- [x] `run --promotion` requires an explicit task path, at least three trials,
+ and accepted calibration for rubric tasks.
+- [x] The public React suite is classified as development evidence; live runs
+ are local and explicitly authorized.
+- [ ] An independently controlled holdout covers positive, negative,
+ ambiguous, near-tie, and adversarial cases from the intended use
+ distribution.
+- [ ] At least two humans label the holdout; disagreements and rationales are
+ retained.
+- [ ] Automated-judge agreement with the retained human labels is measured
+ before a promotion claim.
+- [ ] A repeated-trial promotion run is transcript-reviewed and reports
+ per-dimension outcomes, regressions, variance, usage, and cost.
+
+**Result so far:** The evaluator now distinguishes `development` and
+`promotion` roles and rejects underpowered or uncalibrated rubric promotion
+runs. No holdout content was invented in this repository: independence and
+human labels remain the next evidence gate.
+
+**Dependencies:** Tasks 8 and 9. User approval before adding a provider or
+making paid calls.
diff --git a/tasks/todo.md b/tasks/todo.md
index de6f71f..6b5379d 100644
--- a/tasks/todo.md
+++ b/tasks/todo.md
@@ -1,16 +1,19 @@
# Trustworthy paired evaluation tasks
- [x] Task 1: Lock the qualitative task contract and target-owned suite source.
-- [x] Task 2: Confirm exact skill availability as the intervention.
+- [x] Task 2: Confirm evaluator-owned target-skill instruction injection as the intervention.
- [x] Checkpoint: approve evidence contract and provisional OpenAI judge choice.
- [x] Task 3: Add a provisional Codex judge path.
- [x] Task 3b: Isolate the live Codex home from the host user directory.
- [x] Task 4: Add blinded pairwise comparison.
- [x] Checkpoint: review the first raw judge artifact.
- [x] Task 5: Make the report and exit status quality-aware.
-- [x] Task 6: Calibrate with known outcomes; one-task live pilot recorded.
+- [x] Task 6: Calibrate with known outcomes; live development pilots recorded.
- [ ] Checkpoint: verify all six capabilities (independent judge still open).
-- [ ] Task 7: Lock a non-toy skill suite (ask before inventing one).
+- [x] Task 7: Freeze Vercel's public React skill and an independently authored,
+ externally grounded development benchmark.
- [x] Task 8: Force live calibrate A/B flips; bind accepted fixture hash into `run`.
-- [ ] Task 9: CI gate on complete, hash-bound, non-toy quality evidence.
-- [ ] Task 10: Independent judge or holdout only after the CI gate is honest.
+- [x] Task 9: Deterministic, fake-harness CI protects evaluator mechanics;
+ authorized local runs produce development or promotion evidence.
+- [ ] Task 10: Validate a repeated-trial promotion run on an independently
+ controlled, human-labeled holdout (promotion guardrails implemented).