Skip to content

Semantic eval harness for bundled schemas - #228

Merged
mindsocket merged 7 commits into
mainfrom
claude/project-thread-djt47g
Nov 13, 2026
Merged

mindsocket merged 7 commits into
mainfrom
claude/project-thread-djt47g

Conversation

@mindsocket

@mindsocket mindsocket commented Nov 12, 2026 •

Copy link
Copy Markdown
Owner

Requested by Rog · project thread

Slice 2 of reusable semantic rules: a way to measure how well each provider judges semantic rules, before slice 3 adds primitive rules and type descriptions to the bundled schemas. It covers core's bundled schemas, plugins and configured spaces.

What it adds

  • Terms. A case is a node, a rule and the verdict the node should get. An eval is a .json5 file of cases on one space. A fixture is a space whose nodes live in the suite. A suite is a directory of evals and fixtures.
  • Layout.
    evals/                               harness: load, judge, score, run
    evals/semantic/                      core's suite
      opportunity-customer-voice.json5   { space: 'strict-ost', expected: { <rule id>: { <node id>: verdict } } }
      fixtures.json5                     fixtures as space configs: name, path, schema, plugins
      fixtures/strict-ost.jsonl          parsed nodes, as `sctx dump <space> | jq -c '.nodes[]'` prints them
    
    An eval's space names one of the suite's fixtures, or else a configured space. Fixtures can share a schema, and evals can share a fixture.
  • Suites (--from, default core): core's evals/semantic/, a plugin's evals/semantic/ (found as the config finds plugins), or a path.
  • Runner: bun run eval:semantic --provider <name>… [--from …] [--eval …] [--runs 3] [--per-node] [--check] [--json]. It reports agreement, stability, mean p(expected), reserved verdicts and errors per eval, provider and rule. It lists each miss, and the agreeing cases with the lowest p(expected). --per-node hides evaluateNodes, to test Jev packing.
  • Checks (--check, and before every paid run): a label on a node the rule doesn't judge, an unknown rule or verdict, a label that accepts every verdict, and a labelled node that fails validation. Unlabelled nodes are allowed, as related nodes for scope rules. bun run test checks core's evals.

Design

The harness runs the real pipeline: validateNodes with only the labelled rules and includeGated. It wraps the provider so that only labelled nodes are judged, while the rest still reach the model as related nodes. It needs no change to src/ and adds no exports. Fixtures are read without a parse plugin; a shared core reader can replace that when core #112 gives it a second caller.

Alternatives considered:

  • Call judgeNodes directly. It skips when, expressions and the rule filter.
  • Labels in node frontmatter. Frontmatter goes to the judge, so the label would leak into the input.
  • Fixtures in the space graph data format. Every node would need its resolved edges written by hand.
  • A sctx eval command. It adds public surface; plugin authors need a core checkout until then.

Not run live in the cloud container. A live run on core's eval scored 100% for openrouter-jev, jev --per-node and openai, so slice 3 needs harder cases.

🤖 Generated with Claude Code

https://claude.ai/code/session_019XphpdrUMvqf8fuj8uG7JZ

Fixture spaces labelled with the verdict each node should get per rule, a
live runner that reports agreement, stability, probability on the expected
verdicts, reserved verdicts and errors per provider and rule, and free tests
that keep the labels complete and consistent. Starts with a strict OST space
for opportunity-customer-voice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Xphpdioyw95tAT6PEKGTU
@claude

claude Bot commented Nov 12, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@claude

claude Bot commented Nov 12, 2026

Copy link
Copy Markdown

Code review: correction

My earlier "no issues" comment was wrong. bun run build fails on this PR.

tsconfig.json now includes evals/**/*, and tsconfig.build.json extends it. Its exclude list doesn't mention evals, so the build compiles evals/semantic/*.ts against rootDir: "src" and stops with:

error TS6059: File '.../evals/semantic/run.ts' is not under 'rootDir '.../src'.
  Matched by include pattern 'evals/**/*' in 'tsconfig.build.json'

The fix is to add "evals" to the exclude array in tsconfig.build.json. CLAUDE.md also says to run bun run test:package after changing tsconfig*.json. I haven't run that, so the build is the only check I've made.

I found this from the repo's Stop hook output after the review, not from the review itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Xphpdioyw95tAT6PEKGTU
…paces

--from picks where eval spaces come from: core, a plugin's evals/semantic/,
evals/semantic/ beside the config, or a path. An eval.json5 either names a
schema for its own fixture nodes or labels nodes of a configured space.
Only labelled nodes reach the provider, and --check runs the free checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Xphpdioyw95tAT6PEKGTU
Fixture eval spaces keep their nodes in nodes.jsonl (parsed nodes, one per
line), so editors and spaces never read them as markdown. The config source
waits for the .sctx/ layout; --from <path> covers local eval spaces until then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Xphpdioyw95tAT6PEKGTU
Each line of nodes.jsonl holds the fields frontmatter would, with its title as
node id, matching the JSON node idea in #112.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Xphpdioyw95tAT6PEKGTU
Replaces "eval spaces" with three parts: a fixture (schema and nodes, shared by evals), an eval (one file of labelled cases on a fixture or configured space), and a suite (the directory --from names). Harness code moves out of the suite directory and splits into load, judge and score.

- Unlabelled nodes are allowed as context; the check now reports labels the rule never judges and labels that accept every verdict, from one fake-judge validation pass.
- The report lists the agreeing cases with the lowest p(expected).
- Fixtures are JSON5, so scenarios can carry comments.
- Problems print without a stack trace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XphpdrUMvqf8fuj8uG7JZ
A suite's fixtures.json5 lists fixtures as space configs (name, path to a JSONL nodes file, schema, plugins), so fixtures share schemas the way spaces do. A nodes file holds parsed nodes as `sctx dump` prints them, with their own ids and link targets; the harness no longer derives either from a title. Every eval names a space: a fixture or a configured space, labelled by node id the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XphpdrUMvqf8fuj8uG7JZ
@mindsocket
mindsocket merged commit faa4a41 into main Nov 13, 2026
2 checks passed
@mindsocket
mindsocket deleted the claude/project-thread-djt47g branch November 13, 2026 03:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants