Skip to content

Add a model-in-the-loop eval harness - #26

Open
roborourke wants to merge 1 commit into
mainfrom
feature/eval-harness
Open

roborourke wants to merge 1 commit into
mainfrom
feature/eval-harness

Conversation

@roborourke

@roborourke roborourke commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Closes #17

Reworked for local-only, no API key. Rebased onto main, so it picks up the read/write/delete tool split.

Claude Code is the harness now. Instead of my own turn loop against the SDK, run.mjs boots Playground, writes an MCP config, and runs claude -p. That means a real client against the real protocol rather than a reimplementation of one, and it bills to your subscription.

Two flags do the heavy lifting:

  • --restricted removes Bash and the other code-running tools. Without it the agent can curl the REST API directly and pass without touching MCP at all.
  • --strict-mcp-config ignores your own MCP servers, so a run only sees the test site.

The @anthropic-ai/sdk dependency is gone. package-lock.json is now identical to main's.

--dry-run replaces the scripted provider. It runs setup and the graders with no agent and asserts every scenario fails, which catches a grader that passes when nothing happened. It earned its keep immediately: subscriber-blocked passed under it, because "no post was created" is equally true whether the agent was refused or never ran. That scenario now pairs the forbidden action with a permitted fallback, and the fallback is what proves the run reached the tools and was turned away.

First real run. Previously this was written but unexercised. Now:

pass  categorise-post     (4 turns)
pass  create-post         (3 turns)
pass  delete-post         (4 turns)
pass  discover-routes     (4 turns)
pass  publish-draft       (4 turns)
pass  subscriber-blocked  (4 turns)
pass  upload-image        (3 turns)

7/7 scenarios passed.

No CI wiring, per your note on #27.

🤖 Generated with Claude Code

@roborourke roborourke mentioned this pull request Sep 11, 2026
@roborourke
roborourke force-pushed the feature/mcp-crud-tests branch from c244d98 to f0d4c2e Compare September 11, 2026 14:01
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

Playwright test results

passed  31 passed

Details

stats  31 tests across 5 suites
duration  1 minute, 1 second
commit  1d3d78b

Comment thread tests/evals/providers/anthropic.mjs Outdated
Comment on lines +3 to +14
const DEFAULT_MODEL = 'claude-haiku-4-5';

/**
* Per-million-token prices, for reporting what a run cost.
*
* @see https://www.anthropic.com/pricing
*/
const PRICES = {
'claude-haiku-4-5': { input: 1, output: 5 },
'claude-sonnet-5': { input: 2, output: 10 },
'claude-opus-5': { input: 5, output: 25 },
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need something to keep an eye on these prices or availability of the models?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm good point. It's how well we can attribute the costs but maybe this action can request and show the cost of the run along with the PR comment. Not sure if you can otherwise label API requests. Closest is maybe using the workspace id header in requests but not sure if it allows you to do price based reporting.

Base automatically changed from feature/mcp-crud-tests to main September 15, 2026 10:40

@roborourke roborourke left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're going another route here, as MCP is a protocol it's enough just to test that. This eval is useful for local testing only but we won't use it with CI, so no longer needs to rely on an API key, we can do it through a local subscription.

The end-to-end tests prove the MCP server answers correctly. They can't
prove an agent works out how to use the tools from their descriptions and
schemas alone, which is what regresses quietly when someone rewords a
description or changes a schema.

Claude Code is the harness rather than a turn loop of our own. It
connects to the MCP endpoint itself, so a run exercises a real client
against the real protocol instead of a reimplementation that can drift
from one. It also bills to a local subscription, so there is no API key
anywhere and nothing to wire into CI. The Anthropic SDK dependency goes
with it.

Two CLI flags carry the weight:

- `--restricted` removes Bash and the other code-running tools. Without
  it an agent can curl the REST API directly and pass without ever
  touching MCP, which would make the whole thing meaningless.
- `--strict-mcp-config` ignores the developer's own MCP servers, so a run
  only ever sees the test site.

Grading reads WordPress state over a separate REST connection, never the
agent's account of its own work, so reporting success without changing
anything fails.

`--dry-run` runs the setup and graders with no agent and asserts every
scenario fails. That replaces the scripted provider: it catches a grader
that passes when nothing happened, costs nothing, and needs no model.
It immediately earned its place — subscriber-blocked passed under it,
because "no post was created" is equally true when the agent was refused
and when it never ran. That scenario now pairs the forbidden action with
a permitted fallback, so the fallback is what proves the run reached the
tools and was turned away.

Seven scenarios, all passing against sonnet, in three to four turns each.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@roborourke

Copy link
Copy Markdown
Collaborator Author

Reworked for local-only, no API key. Rebased onto main, so it picks up the read/write/delete tool split.

Claude Code is the harness now. Instead of my own turn loop against the SDK, run.mjs boots Playground, writes an MCP config, and runs claude -p. That means a real client against the real protocol rather than a reimplementation of one, and it bills to your subscription.

Two flags do the heavy lifting:

  • --restricted removes Bash and the other code-running tools. Without it the agent can curl the REST API directly and pass without touching MCP at all.
  • --strict-mcp-config ignores your own MCP servers, so a run only sees the test site.

The @anthropic-ai/sdk dependency is gone. package-lock.json is now identical to main's.

--dry-run replaces the scripted provider. It runs setup and the graders with no agent and asserts every scenario fails, which catches a grader that passes when nothing happened. It earned its keep immediately: subscriber-blocked passed under it, because "no post was created" is equally true whether the agent was refused or never ran. That scenario now pairs the forbidden action with a permitted fallback, and the fallback is what proves the run reached the tools and was turned away.

First real run. Previously this was written but unexercised. Now:

pass  categorise-post     (4 turns)
pass  create-post         (3 turns)
pass  delete-post         (4 turns)
pass  discover-routes     (4 turns)
pass  publish-draft       (4 turns)
pass  subscriber-blocked  (4 turns)
pass  upload-image        (3 turns)

7/7 scenarios passed.

No CI wiring, per your note on #27.

🤖 Generated with Claude Code

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.

Add a model-in-the-loop eval harness

2 participants