feat(evals): SkillGym-inspired DX layer (authoring façade, reporter lifecycle, workspace presets, token snapshots)#37
Merged
Merged
Conversation
…t, apply to pilots
…hold/--snapshot-dir flags
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a thin, SkillGym-inspired developer-experience layer to the
evals/package without replacing the current eval engine. Case authoring becomes intention-revealing, observability becomes first-class, workspace setup becomes reusable, and token regressions become a dedicated sidecar signal — all while preserving every existing schema, runner, scorer, statistic, and report.This is the orchestrator-delivered output of a six-phase plan; 36 commits spanning Phases 0+1 through Phase 6. Each phase landed via a dedicated sub-agent task whose patch was dry-run-applied and then integrated here.
What changed, per feature area
1. Authoring façade (
evals/authoring/)Fluent builders that compile to the existing
PromptEvalCaseSchema | ExecutionEvalCaseSchema | DogfoodEvalCaseSchemaruntime contract.promptCase(id)/executionCase(id)/dogfoodCase(id)with a three-tier abstraction: high-level sugar, mid-level typed builders, and raw escape hatches (rawWorkflowCheck,rawVerifier,rawArtifactRequirement,rawReportRequirement).build()ends in a strict Zod.parse(), so the lane runners/scorers see identical objects to before.wait-for-output(prompt),hello-prompt+doctor-gated(execution),exploratory-qa(dogfood). Exported identifiers preserved so runner imports are untouched.2. Typed reporter lifecycle (
evals/reporters/)Zero-impact by default; additive when opted in.
Reporterinterface with callbacks forrun.start / lane.start / case.start / trial.start / trial.finish / case.finish / lane.finish / run.finish.ReporterDispatchervalidates every event payload through strict Zod schemas, redacts secret-looking env values (*_TOKEN,*_KEY,*_SECRET,*_PASSWORD), and isolates reporter failures.ConsoleReporter(hierarchical progress on stderr),JsonlReporter(one JSON event per line),FinalReportReporter(adapter wrapping existinggenerateJsonReport()/generateMarkdownReport()).--reporter <name>(repeatable),--reporter-output <path>,--progress. Default behavior when no flags are supplied is byte-identical to pre-change.3. Workspace presets (
evals/workspaces/)Declarative, additive workspace setup for execution + dogfood lanes.
WorkspacePresetschema:{ id, mode, description, templateDir?, bootstrap?, cwd?, env? }with a registry + resolver.templateDir, and documents a deterministic merge order (preset env/bootstrap first, then case-level overrides/setup)..workspace(presetId).CaseStartEventgains an optional, redactedworkspacepayload.agent-tty-smokepreset registered viaevals/workspaces/builtins.ts; applied tohello-promptandexploratory-qaas pilots.4. Token usage + opt-in snapshot guardrails (
evals/snapshots/)Cost regression signal, deliberately orthogonal to quality scoring.
TokenUsageadded to the normalized provider output contract. Populated byclaude.tsandcodex.tswhen the upstream CLI emits usage;fixtures.tscan emit deterministic usage for tests.token-usage.jsonsidecar artifacts viaevals/lib/artifacts.ts.(provider, model, lane, caseId, condition, caseFingerprint)wherecaseFingerprintis a stable SHA-256 over the case's semantic fields.tokenReportsection added to final JSON + Markdown reports when any trial emits usage; the section is cleanly absent otherwise.--snapshot-update,--snapshot-check,--snapshot-threshold <percent>(default 20),--snapshot-dir <path>. Conflicting flags rejected at parse time.EvalResult.okor influence scoring / statistics / comparison. No enforce flag ships in this phase by design.5. Docs
evals/README.mdgains dedicated sections for authoring façade, reporter lifecycle, workspace presets, and token snapshots..mux/skills/eval-guide/SKILL.mdupdated so agent workflows use the new surfaces.dogfood/CATALOG.mdreferences the Phase 5 proof bundle atdogfood/token-usage-phase5-proof/.Backward compatibility contract
Hard invariants preserved across the entire change:
getAllPromptCases()/getAllExecutionCases()/getAllDogfoodCases()registration mechanism unchanged.report.json/report.mdpaths and shapes.EvalResult.oknever flips on a regression.Validation
npm run verify(typecheck + lint + format:check + 976 tests + build + smoke install) passes locally in ~1m55s.Representative dogfood runs driven by
stub+fixtureproviders exercise:--progressconsole reporter emitting hierarchical lifecycle lines on stderr.--reporter jsonlproducing a 20-event stream with consistentrunIdand full payload schemas.--reporter final --reporter jsonlproducing both report files and the event stream.agent-tty-smoke) appearing incase.startevents for migrated pilots and absent for unmigrated cases.--snapshot-update→ baseline,--snapshot-checkwith same tokens →unchanged,--snapshot-checkwith +30% tokens at threshold=20 →regressedwithokstilltrue.Known follow-ups (non-blocking, documented in
dogfood/CATALOG.md)dogfood/proof bundle; Phases 1–4 rely on automated tests and the in-PR walkthrough above. Reviewer-captured bundles would be a small additive follow-up.(lane, caseId, condition)— with--trials > 1, files overwrite and the payload records the last trial.trialIndexis already in the payload; upgrading the path is additive.caseFingerprintis in the payload, not the sidecar path. Additive if needed..tokenBudget(...)authoring sugar was intentionally deferred until snapshot semantics settle in production use.--reporter jsonlalone replaces the implicitfinalreporter rather than stacking; users who want both must pass--reporter final --reporter jsonl. Documented, but a reviewer-facing gotcha..workspace()— intentional Phase 4 pilot scope; broader migration is opportunistic per docs.Commit map (36 commits)
📋 Implementation Plan
SkillGym-inspired eval DX implementation plan
1. Desired outcome
Add a thin, ergonomic developer interface for eval authoring and execution without replacing the current eval engine.
At the end of this work, the repo should have:
2. Repo facts that shape the plan
The plan is intentionally grounded in the current code layout and verified repo structure:
evals/run.tsis the main orchestration entrypoint. It parses CLI args, resolves lanes/conditions/cases, runs the lane runners, and writes final JSON/Markdown reports.evals/lib/types.tsandevals/lib/schemas.tsare the canonical runtime contracts for cases, results, reports, and provider output.evals/prompt/runner.ts,evals/execution/runner.ts, andevals/dogfood/runner.tseach aggregate hard-coded case exports viagetAllPromptCases(),getAllExecutionCases(), andgetAllDogfoodCases().evals/prompt/cases/trigger-agent-tty.ts), execution authoring has the richest helper layer (evals/execution/cases/shared.ts), and dogfood authoring is powerful but regex-heavy (evals/dogfood/cases/exploratory-qa.ts).evals/lib/scheduler.tsalready centralizes concurrent scheduling and already has alogLinehook, which is a good seam for later reporter events.evals/lib/cliHarness.tsalready creates isolated eval homes, so workspace presets should extend existing behavior rather than invent a separate setup mechanism.evals/providers/base.tsand provider implementations) already normalizes outputs, but token usage is not currently part of the normalized result contract.These facts strongly favor an additive façade over a rewrite.
3. Goals and non-goals
Goals
Non-goals
PromptEvalCase | ExecutionEvalCase | DogfoodEvalCaseas the runner/scoring contract in the first implementation.generateJsonReport()/generateMarkdownReport()as the first reporter milestone.4. Architectural decisions
evals/authoring/façade that compiles to existing case objectsinvariant, strict Zod, fail-fast)Design rule for the authoring layer
The new authoring surface should offer three levels of abstraction:
createSession(),input(),waitFor(),snapshot(),destroy(),requiresScreenshot(),findingsWithSeverity().rawWorkflowCheck(...),rawVerifier(...),rawArtifactRequirement(...),rawReportRequirement(...).That layering prevents DSL overreach from blocking unusual cases.
5. Target module layout
New public authoring surface
evals/authoring/index.tsevals/authoring/prompt.tsevals/authoring/execution.tsevals/authoring/dogfood.tsevals/authoring/workflow.tsevals/authoring/report.tsevals/authoring/compile.tsevals/authoring/raw.tsNew reporter surface
evals/reporters/types.tsevals/reporters/dispatch.tsevals/reporters/console.tsevals/reporters/jsonl.tsevals/reporters/final-report.tsNew workspace support
evals/workspaces/types.tsevals/workspaces/registry.tsevals/workspaces/resolver.tsNew token snapshot support
evals/snapshots/schema.tsevals/snapshots/store.tsevals/snapshots/compare.tsExisting files likely to change
evals/lib/types.tsevals/lib/schemas.tsevals/run.tsevals/lib/scheduler.tsevals/lib/artifacts.tsevals/lib/cliHarness.tsevals/providers/base.tsevals/providers/claude.tsevals/providers/codex.tsevals/prompt/runner.tsevals/execution/runner.tsevals/dogfood/runner.tsevals/README.md.mux/skills/eval-guide/SKILL.mddogfood/README.mddogfood/CATALOG.md6. Phase 0 — guardrails, pilot scope, and parity baseline
Goal
Create the safety rails that keep the implementation from turning into a rewrite.
Work items
Document explicit invariants at the top of the implementation work.
Choose a small pilot set of cases to prove the façade.
evals/prompt/cases/trigger-agent-tty.tssuch aswait-for-output.evals/execution/cases/hello-prompt.tsandevals/execution/cases/doctor-gated.ts.evals/dogfood/cases/exploratory-qa.ts.Capture baseline outputs for pilot cases before migrating them.
Decide the initial public API shape and freeze it before broad migration.
Acceptance criteria
7. Phase 1 — authoring façade MVP
Goal
Introduce a better developer interface for authoring cases, but compile it down to the current case schemas so the rest of the pipeline can stay intact.
Scope
This phase is only about case authoring ergonomics. It should not yet require reporter, workspace, or token snapshot rewrites.
Implementation approach
7.1 Create lane-specific builder entrypoints
Implement public builders under
evals/authoring/:promptCase(id)executionCase(id)dogfoodCase(id)Each builder should collect author intent and then call
build()to produce a schema-validated case object.7.2 Compile to the current runtime shapes
build()should ultimately call the same schema validation the repo already trusts:PromptEvalCaseSchema.parse(...)ExecutionEvalCaseSchema.parse(...)DogfoodEvalCaseSchema.parse(...)That preserves the current strict runtime contract.
7.3 Reuse existing helper logic instead of duplicating it
The new builder layer should reuse or wrap existing helper logic where available, especially from:
evals/execution/cases/shared.tsevals/dogfood/cases/shared.tsDo not fork pattern logic into two divergent implementations if the repo already has a helper that encodes the intended behavior.
7.4 Start with the recurring “happy path” primitives
Prompt lane primitives
.category(...).prompt(...).expectSkill(...).context(...).workflow((w) => ...).mustMention(...).mustNotMention(...).budget(...).rawWorkflowCheck(...)Execution lane primitives
.category(...).fixture(...).task(...).workflow((w) => ...)w.createSession()w.run(...)and/orw.input(...)w.waitFor(...)w.snapshot()w.screenshot()w.destroy().assertions((a) => ...).artifact(...).budget(...).rawVerifier(...).rawWorkflowCheck(...)Dogfood lane primitives
.category(...).fixture(...).task(...).proofBundle((b) => ...)b.requiresScreenshot()b.requiresRecording()b.requiresNotes().report((r) => ...)r.title()r.reproductionSteps()r.findingsWithSeverity()r.evidenceReferences().validationProfile(...).budget(...).rawArtifactRequirement(...).rawReportRequirement(...).rawVerifier(...)7.5 Preserve manual registration in lane runners
Do not add automatic case discovery in v1.
Continue to export regular case constants from case files and continue importing them into:
getAllPromptCases()getAllExecutionCases()getAllDogfoodCases()This keeps the runner/dry-run/reporting behavior stable while validating the new authoring layer.
7.6 Pilot migration strategy
Use the new authoring façade for the pilot cases only:
wait-for-outputhello-promptdoctor-gatedexploratory-qaPreserve the exported identifiers used by the runner imports so the runner diff stays small.
7.7 Target API sketches for the pilot cases
These are target-shape sketches, not final syntax commitments. They exist to keep the implementation aligned with the DX goal.
The implementation does not need to match the exact method names above if a simpler variant is clearly better, but the resulting authoring experience should be comparably concise and intention-revealing.
Acceptance criteria
Notes on minimalism
Avoid turning this phase into a giant type-level DSL. The initial builder implementation should prioritize:
8. Phase 2 — golden parity tests and migration safety
Goal
Prove that the authoring façade changes how cases are written, not what the engine sees.
Work items
Add unit tests for builder compilation.
test/unit/evals/authoring/.build()emits schema-valid case objects.Add golden parity tests for pilot cases.
Add lane-level integration tests.
Defensively assert builder invariants.
build().Recommended test files
test/unit/evals/authoring/prompt.test.tstest/unit/evals/authoring/execution.test.tstest/unit/evals/authoring/dogfood.test.tstest/integration/evals/authoring-parity.test.tsAcceptance criteria
9. Phase 3 — typed reporter lifecycle and live progress
Goal
Expose the eval lifecycle to built-in and custom reporters without rewriting the core scoring/report generation path.
Implementation approach
9.1 Add a typed reporter contract
Create a small typed reporter surface in
evals/reporters/types.ts.Recommended callback surface:
onRunStartonLaneStartonCaseStartonTrialStartonTrialFinishonCaseFinishonLaneFinishonRunFinishKeep the event surface coarse-grained. Do not emit every internal implementation detail.
9.2 Add a reporter dispatcher
Implement
evals/reporters/dispatch.tsas a small fan-out layer that accepts zero or more reporters and safely invokes them.Requirements:
9.3 Integrate reporters through
evals/run.tsandevals/lib/scheduler.tsevals/run.tsdown into lane execution.evals/lib/scheduler.tswith start/finish hooks around scheduled work items.9.4 Keep current final reports as adapters first
Do not rewrite
generateJsonReport()/generateMarkdownReport()at the start of this phase.Instead:
This keeps the diff small and the risk low.
9.5 Ship two built-ins first
Console progress reporter
JSONL reporter
Suggested CLI additions
Additive flags only, for example:
--reporter console--reporter jsonl--reporter-output <path>--progress(sugar for console reporter)9.6 Reporter API sketch
A minimal typed reporter contract should look roughly like this:
The first implementation should keep event payloads focused on identities, timing, outcome, and artifact/report paths. Avoid dumping every internal structure into every callback.
Acceptance criteria
10. Phase 4 — workspace presets for execution and dogfood
Goal
Make workspace setup explicit, reusable, and easier to dogfood while preserving the current isolated-home model.
Implementation approach
10.1 Introduce a workspace preset model
Create a small preset type in
evals/workspaces/types.ts, roughly covering:isolated | sharedKeep it small. Avoid a full-blown environment orchestration framework.
10.2 Resolve presets through existing CLI harness behavior
Use
evals/lib/cliHarness.tsas the integration seam.The preset resolver should extend the existing isolated-home creation path rather than bypass it.
10.3 Integrate presets into execution/dogfood runners first
Prompt lane does not need workspace presets in the first pass.
Execution and dogfood runners should be able to:
10.4 Keep legacy setup steps valid
Workspace presets should augment the current
setupapproach first, not replace it.That means:
setupsteps still run,10.5 Log resolved workspace config for observability
At minimum, expose the resolved workspace plan in:
Secrets must be redacted.
10.6 Workspace preset sketch
A deliberately small preset model is enough for the first pass:
If a case uses a preset plus explicit setup steps, the implementation should document and test the merge order rather than leaving it implicit.
Acceptance criteria
11. Phase 5 — token usage capture and snapshot guardrails
Goal
Add a lightweight cost regression signal without entangling it with the core quality score.
Implementation approach
11.1 Normalize token usage where providers expose it
Add a small optional token usage type to the provider/result path.
Recommended fields:
If a provider cannot expose token data, the field should remain absent rather than guessed.
11.2 Store token data as a sidecar artifact first
Do not immediately force token data into the main pass/fail score path.
Instead:
evals/lib/artifacts.ts,EvalResult.oksemantics unchanged.11.3 Add a snapshot store keyed by stable identifiers
Token snapshots should be keyed by at least:
The fingerprint matters so that prompt or workflow changes do not create bogus regressions against an obsolete baseline.
11.4 Add opt-in CLI flows before per-case budgets
Start with CLI controls such as:
--snapshot-update--snapshot-check--snapshot-threshold <percent>--snapshot-dir <path>Only after the storage and reporting semantics are stable should the authoring layer grow convenience helpers such as
.tokenBudget(...).11.5 Keep snapshot outcomes separate from quality scoring
By default:
ok,If the repo later wants enforcement, add an explicit opt-in mode such as
--snapshot-enforce.11.6 Snapshot CLI sketch
A minimal first-pass CLI could look like this:
If the repo later wants case-level ergonomics, add that only after the storage/report semantics are stable, for example:
That sequence keeps the storage contract ahead of the authoring sugar.
Acceptance criteria
12. Phase 6 — docs, migration, and cleanup
Goal
Turn the pilot architecture into a maintainable, teachable workflow for future contributors.
Work items
Document the new authoring path.
evals/README.mdwith before/after examples and migration guidance..mux/skills/eval-guide/SKILL.mdso agent workflows can use the new interface correctly.Document the reporter model.
Document workspace preset usage.
SkillCondition.Document token snapshot semantics.
Migrate additional cases opportunistically, not all at once.
Acceptance criteria
13. Detailed file-by-file implementation checklist
Core contracts
evals/lib/types.tsReadonlyArray-style discipline where appropriate.evals/lib/schemas.ts.strict()semantics.Authoring layer
evals/authoring/index.tsevals/authoring/prompt.tsevals/authoring/execution.tsevals/authoring/dogfood.tsevals/authoring/raw.tsRunner/reporter integration
evals/run.tsevals/lib/scheduler.tsevals/prompt/runner.ts,evals/execution/runner.ts,evals/dogfood/runner.tsWorkspace support
evals/lib/cliHarness.tsevals/workspaces/*Token snapshots
evals/providers/base.tsevals/providers/claude.ts,evals/providers/codex.tsevals/lib/artifacts.tsevals/snapshots/*Docs and proof
evals/README.md.mux/skills/eval-guide/SKILL.mddogfood/README.md,dogfood/CATALOG.md14. Backward compatibility contract
The implementation should preserve these guarantees throughout the rollout:
15. Risks and mitigations
16. Validation strategy
Automated validation gates
Run these before claiming the work complete:
mise run formatmise run lintmise run typecheckmise run testmise run ciIf
miseis unavailable, fall back to the repo’s documented direct commands.Targeted test focus
Defensive-programming checks
Implementation should deliberately assert:
build(),17. Dogfooding and proof plan
This project is CLI-first, so the dogfooding plan needs to show both authoring DX and runtime UX. The proof bundle should be reviewer-friendly and include screenshots plus video/recording evidence.
17.1 Setup environment
mise installmise run bootstrapmiseis unavailable:npm cinpx playwright install chromiumAGENT_TTY_HOMEfor dogfood runs.17.2 Dogfood milestones
Milestone A — authoring façade proof
Milestone B — reporter proof
Milestone C — workspace preset proof
Milestone D — token snapshot proof
17.3 Suggested proof-bundle contents
Create a dated proof bundle under
dogfood/containing at minimum:README.mdorindex.mdsummary,.png),.cast) and exported video (.webm) where relevant,Update
dogfood/CATALOG.mdso reviewers can find it quickly.17.4 Suggested dogfood commands
Use direct CLI invocations from the repo while developing, for example:
npx tsx evals/run.ts --provider stub --lane prompt --case wait-for-output --dry-runnpx tsx evals/run.ts --provider fixture --lane execution --case hello-prompt --jsonnpx tsx evals/run.ts --provider fixture --lane dogfood --case exploratory-qa --jsonCapture screenshots and recordings with the project’s own terminal workflow where practical so the reviewer can inspect the exact terminal UX being claimed.
Dogfooding acceptance criteria
18. Recommended implementation order
This ordering minimizes risk because it validates the authoring façade before expanding the runtime surface area.
19. Exit criteria for the overall initiative
The work is ready to call successful when all of the following are true:
20. Short implementation summary
The safest and most valuable path is:
evals/authoringfaçade,Generated with
mux• Model:anthropic:claude-opus-4-7• Thinking:max