Skip to content

feat(eval): OpenRouter CI tier on DeepSeek V4.1 Flash - #2

Open
manykarim wants to merge 4 commits into
mainfrom
change/openrouter-ci-eval-tier
Open

manykarim wants to merge 4 commits into
mainfrom
change/openrouter-ci-eval-tier

Conversation

@manykarim

Copy link
Copy Markdown
Collaborator

Why

The live skill evals only run on Claude, billed to a maintainer's subscription. A spike (run 42822700497) ran the rf-core narrow tier on DeepSeek V4.1 Flash through OpenRouter: all 72 sessions completed, the skills loaded in every treatment run, the treatment arm passed 11 of 12 tasks, and the arm delta stayed visible on 8 tasks. OpenRouter charged $0.84. This PR makes that the default CI tier when the OPENROUTER_API_KEY secret is set. OpenSpec change: openspec/changes/openrouter-ci-eval-tier/.

What changes

  • Provider. An OpenRouter model id (--model deepseek/deepseek-v4.1-flash) selects the OpenRouter provider. The runner points Claude Code at OpenRouter, maps every model alias to DeepSeek, and removes Claude credentials from the session. A session that used any other model is recorded as incomplete (provider_model).
  • Model rules. OpenRouter models are allowed on the command line only; task files can't declare them. Other OpenRouter ids are rejected.
  • Cost. OpenRouter runs are priced at list prices, not with Claude Code's Claude-priced figure ($29.66 for the spike). --max-cost-usd and the reports use the estimate, and the reports label it as one.
  • Turn limits. --max-turns-factor (CI uses 1.5) scales each task's max_turns; 25 of the 72 spike sessions hit the limit at 1.0. The factor is stored with runs and baselines; reports count turn-limit stops.
  • Baselines per model. DeepSeek baselines go in plugins/<plugin>/evals/baselines/openrouter/deepseek-v4.1-flash/; Claude baselines stay where they are. A model or factor mismatch is rebaseline-needed.
  • CI. Preflight picks the provider (OpenRouter if its key is set, otherwise Claude, overridable with the new provider dispatch input). Each job gets only that provider's credentials and runs with separate OpenRouter caps. OpenRouter jobs run a one-session smoke test and report the key's real spend before and after (scripts/openrouter-ci.py).
  • Docs. docs/ci/usage.md (new "OpenRouter tier" section), docs/ci/local-testing.md, an ADR-005 amendment, a Claude check in RELEASING.md, and .env.example.

Before this gates anything

  • There are no DeepSeek baselines yet, so this PR's own eval jobs will report rebaseline-needed (exit 3). The next step is a manual dispatch on this branch (provider=openrouter, all tiers, both arms, N=6) and a reviewed PR with its candidate baselines (tasks 6.1–6.2).
  • List prices put the spike at $1.33 against $0.84 actually charged, so the estimate runs high. That only makes the caps stop a run early; it never lets one overspend. Task 3.2 (calibrate within ±25%) is still open for a decision.

Testing

  • pytest tests passes: 1430 passed, 1 skipped (gitleaks not installed locally). The new tests are in tests/eval/test_providers.py and tests/eval/test_ci_preflight.py; the preflight tests run the provider-selection script for each credential combination.
  • ruff, mypy, the docs-voice check and openspec validate --strict pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FHEmzL3ErAhGQykKWDx2wP

PR and weekly skill evals run on deepseek/deepseek-v4.1-flash through
OpenRouter when the OPENROUTER_API_KEY secret is set, so they no longer
need a maintainer's Claude subscription. Claude runs become a manual
dispatch (provider: claude) and a step in the release checklist.

Harness:
- OpenRouter model ids select the openrouter provider; the runner sets
  the OpenRouter environment itself and strips Claude credentials. A
  session that used another model is recorded as incomplete
  (provider_model).
- OpenRouter models are run-time only; task files may not declare them.
- OpenRouter runs are priced from list prices (domain/pricing.py)
  instead of Claude Code's Claude-priced total_cost_usd.
- --max-turns-factor scales each task's max_turns; the factor is stored
  with runs and baselines, and turn-limit stops are reported.
- Baselines are kept per model (baselines/openrouter/<model name>/);
  gate and baseline update find them from the runs' model.

CI: preflight picks the provider, each job gets only that provider's
credentials, OpenRouter caps, a smoke session, and the key's real
spend before and after (scripts/openrouter-ci.py).

OpenSpec change: openrouter-ci-eval-tier.

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

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Skill Evaluation: rf-desktop (narrow, treatment, N=3, deepseek/deepseek-v4.1-flash)

Model: deepseek/deepseek-v4.1-flash (provider openrouter, turn-limit factor 2.0)

OpenRouter spend

USD
Key spend during this job (includes concurrent jobs) 0.31
Harness estimate (OpenRouter list prices) 0.12
Credit remaining on the key 32.054726387

Gate

gate: REBASELINE-NEEDED (1 task(s) checked)
- [rebaseline-needed] narrow-platynui-calculator-01: no stored baseline entry

rf-skill-eval report

Runs: 3 · pass: 2 · fail: 1 · incomplete: 0 · skipped checks: 0 · estimated cost (OpenRouter list prices): $0.12 · turn-limit stops: 1

Pass rate = runs whose gate result is pass / runs attempted. Deltas are treatment − baseline over outcome checks only.

Tasks

Task Skill Tier Arm Runs Pass rate Outcome pass Incomplete Input tok Output tok Turns Duration s Cost $ Flaky
narrow-platynui-calculator-01 rf-platynui narrow treatment 3 67% 67% 0 980780 14579 30.7 (1 at limit) 114.2 0.0404 (est.) yes
narrow-platynui-calculator-01 rf-platynui narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable

Skills

Skill Tasks Treatment outcome pass Baseline outcome pass Δ outcome pass Δ input tok Δ turns Δ cost $
rf-platynui 1 67% – unavailable unavailable unavailable unavailable

Skipped / errored checks

None.

Incomplete runs

None.

Process checks (treatment arm, not in deltas)

  • narrow-platynui-calculator-01 loaded_rf_platynui_skill: 3/3

Adversarial tier (reported only, never gates)

None.

@github-actions

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Skill Evaluation: rf-mobile (narrow, treatment, N=3, deepseek/deepseek-v4.1-flash)

Model: deepseek/deepseek-v4.1-flash (provider openrouter, turn-limit factor 1.5)

OpenRouter spend

USD
Key spend during this job (includes concurrent jobs) 0.16
Harness estimate (OpenRouter list prices) 0.08
Credit remaining on the key 32.217112543

Gate

gate: REBASELINE-NEEDED (1 task(s) checked)
- [rebaseline-needed] narrow-appium-login-01: no stored baseline entry

rf-skill-eval report

Runs: 3 · pass: 3 · fail: 0 · incomplete: 0 · skipped checks: 0 · estimated cost (OpenRouter list prices): $0.08 · turn-limit stops: 1

Pass rate = runs whose gate result is pass / runs attempted. Deltas are treatment − baseline over outcome checks only.

Tasks

Task Skill Tier Arm Runs Pass rate Outcome pass Incomplete Input tok Output tok Turns Duration s Cost $ Flaky
narrow-appium-login-01 rf-appium narrow treatment 3 100% 100% 0 433032 9042 21.7 (1 at limit) 57.7 0.0263 (est.) no
narrow-appium-login-01 rf-appium narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable

Skills

Skill Tasks Treatment outcome pass Baseline outcome pass Δ outcome pass Δ input tok Δ turns Δ cost $
rf-appium 1 100% – unavailable unavailable unavailable unavailable

Skipped / errored checks

None.

Incomplete runs

None.

Process checks (treatment arm, not in deltas)

  • narrow-appium-login-01 loaded_rf_appium_skill: 3/3

Adversarial tier (reported only, never gates)

None.

@github-actions

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Skill Evaluation: rf-web (narrow, treatment, N=3, deepseek/deepseek-v4.1-flash)

Model: deepseek/deepseek-v4.1-flash (provider openrouter, turn-limit factor 1.5)

OpenRouter spend

USD
Key spend during this job (includes concurrent jobs) 0.35
Harness estimate (OpenRouter list prices) 0.16
Credit remaining on the key 31.948323853

Gate

gate: REBASELINE-NEEDED (2 task(s) checked)
- [rebaseline-needed] narrow-browser-login-01: no stored baseline entry
- [rebaseline-needed] narrow-selenium-login-01: no stored baseline entry

rf-skill-eval report

Runs: 6 · pass: 6 · fail: 0 · incomplete: 0 · skipped checks: 0 · estimated cost (OpenRouter list prices): $0.16 · turn-limit stops: 1

Pass rate = runs whose gate result is pass / runs attempted. Deltas are treatment − baseline over outcome checks only.

Tasks

Task Skill Tier Arm Runs Pass rate Outcome pass Incomplete Input tok Output tok Turns Duration s Cost $ Flaky
narrow-browser-login-01 rf-browser narrow treatment 3 100% 100% 0 385123 5732 21.7 59.2 0.0267 (est.) no
narrow-browser-login-01 rf-browser narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable
narrow-selenium-login-01 rf-selenium narrow treatment 3 100% 100% 0 457054 7332 21.0 (1 at limit) 81.6 0.0259 (est.) no
narrow-selenium-login-01 rf-selenium narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable

Skills

Skill Tasks Treatment outcome pass Baseline outcome pass Δ outcome pass Δ input tok Δ turns Δ cost $
rf-browser 1 100% – unavailable unavailable unavailable unavailable
rf-selenium 1 100% – unavailable unavailable unavailable unavailable

Skipped / errored checks

None.

Incomplete runs

None.

Process checks (treatment arm, not in deltas)

  • narrow-browser-login-01 loaded_rf_browser_skill: 3/3
  • narrow-selenium-login-01 loaded_rf_selenium_skill: 3/3

Adversarial tier (reported only, never gates)

None.

@github-actions

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Skill Evaluation: rf-api (narrow, treatment, N=3, deepseek/deepseek-v4.1-flash)

Model: deepseek/deepseek-v4.1-flash (provider openrouter, turn-limit factor 1.5)

OpenRouter spend

USD
Key spend during this job (includes concurrent jobs) 0.42
Harness estimate (OpenRouter list prices) 0.17
Credit remaining on the key 31.948323853

Gate

gate: REBASELINE-NEEDED (2 task(s) checked)
- [rebaseline-needed] narrow-requests-users-01: no stored baseline entry
- [rebaseline-needed] narrow-restinstance-schema-01: no stored baseline entry

rf-skill-eval report

Runs: 6 · pass: 6 · fail: 0 · incomplete: 0 · skipped checks: 0 · estimated cost (OpenRouter list prices): $0.17 · turn-limit stops: 2

Pass rate = runs whose gate result is pass / runs attempted. Deltas are treatment − baseline over outcome checks only.

Tasks

Task Skill Tier Arm Runs Pass rate Outcome pass Incomplete Input tok Output tok Turns Duration s Cost $ Flaky
narrow-requests-users-01 rf-requests narrow treatment 3 100% 100% 0 498354 8219 26.0 (1 at limit) 65.0 0.0296 (est.) no
narrow-requests-users-01 rf-requests narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable
narrow-restinstance-schema-01 rf-restinstance narrow treatment 3 100% 100% 0 398648 7472 20.3 (1 at limit) 98.7 0.0256 (est.) no
narrow-restinstance-schema-01 rf-restinstance narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable

Skills

Skill Tasks Treatment outcome pass Baseline outcome pass Δ outcome pass Δ input tok Δ turns Δ cost $
rf-requests 1 100% – unavailable unavailable unavailable unavailable
rf-restinstance 1 100% – unavailable unavailable unavailable unavailable

Skipped / errored checks

None.

Incomplete runs

None.

Process checks (treatment arm, not in deltas)

  • narrow-requests-users-01 loaded_rf_requests_skill: 3/3
  • narrow-restinstance-schema-01 loaded_rf_restinstance_skill: 3/3

Adversarial tier (reported only, never gates)

None.

…ping

The first OpenRouter baseline run (42885585678) failed its Doctor step,
skipped the tiers, and still ran the trigger evals, because that step
was `if: always()`. Trigger evals now need a successful Doctor. The
OpenRouter auth ping waits 120 s, like the smoke step, instead of 30 s.

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

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Skill Evaluation: rf-core (narrow, treatment, N=3, deepseek/deepseek-v4.1-flash)

Model: deepseek/deepseek-v4.1-flash (provider openrouter, turn-limit factor 1.5)

OpenRouter spend

USD
Key spend during this job (includes concurrent jobs) 1.23
Harness estimate (OpenRouter list prices) 0.77
Credit remaining on the key 41.08948771

Gate

gate: REBASELINE-NEEDED (12 task(s) checked)
- [rebaseline-needed] narrow-language-bdd-01: no stored baseline entry
- [rebaseline-needed] narrow-language-data-driven-01: no stored baseline entry
- [rebaseline-needed] narrow-language-embedded-01: no stored baseline entry
- [rebaseline-needed] narrow-language-env-varfiles-01: no stored baseline entry
- [rebaseline-needed] narrow-language-suite-setup-tags-01: no stored baseline entry
- [rebaseline-needed] narrow-non-rf-control-01: no stored baseline entry
- [rebaseline-needed] narrow-python-library-flaky-listener-01: no stored baseline entry
- [rebaseline-needed] narrow-python-library-inventory-scope-01: no stored baseline entry
- [rebaseline-needed] narrow-python-library-mode-literal-01: no stored baseline entry
- [rebaseline-needed] narrow-rf-injection-positive-01: no stored baseline entry
- [rebaseline-needed] narrow-rf-mcp-execute-01: no stored baseline entry
- [rebaseline-needed] narrow-setup-requests-01: no stored baseline entry

rf-skill-eval report

Runs: 36 · pass: 36 · fail: 0 · incomplete: 0 · skipped checks: 0 · estimated cost (OpenRouter list prices): $0.77 · turn-limit stops: 9

Pass rate = runs whose gate result is pass / runs attempted. Deltas are treatment − baseline over outcome checks only.

Tasks

Task Skill Tier Arm Runs Pass rate Outcome pass Incomplete Input tok Output tok Turns Duration s Cost $ Flaky
narrow-language-bdd-01 rf-language narrow treatment 3 100% 100% 0 332077 3730 19.7 40.9 0.0186 (est.) no
narrow-language-bdd-01 rf-language narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable
narrow-language-data-driven-01 rf-language narrow treatment 3 100% 100% 0 506102 7191 26.7 65.1 0.0226 (est.) no
narrow-language-data-driven-01 rf-language narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable
narrow-language-embedded-01 rf-language narrow treatment 3 100% 100% 0 436285 6851 20.7 54.8 0.0186 (est.) no
narrow-language-embedded-01 rf-language narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable
narrow-language-env-varfiles-01 rf-language narrow treatment 3 100% 100% 0 763467 16816 22.0 (3 at limit) 149.4 0.0416 (est.) no
narrow-language-env-varfiles-01 rf-language narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable
narrow-language-suite-setup-tags-01 rf-language narrow treatment 3 100% 100% 0 407383 3993 21.0 52.6 0.0166 (est.) no
narrow-language-suite-setup-tags-01 rf-language narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable
narrow-non-rf-control-01 plugin narrow treatment 3 100% 100% 0 27688 224 2.0 3.9 0.0059 (est.) no
narrow-non-rf-control-01 plugin narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable
narrow-python-library-flaky-listener-01 rf-python-library narrow treatment 3 100% 100% 0 533804 10929 22.3 (1 at limit) 67.0 0.0254 (est.) no
narrow-python-library-flaky-listener-01 rf-python-library narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable
narrow-python-library-inventory-scope-01 rf-python-library narrow treatment 3 100% 100% 0 373256 6289 17.3 (1 at limit) 55.6 0.0202 (est.) no
narrow-python-library-inventory-scope-01 rf-python-library narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable
narrow-python-library-mode-literal-01 rf-python-library narrow treatment 3 100% 100% 0 513936 13770 22.0 (2 at limit) 119.0 0.0285 (est.) no
narrow-python-library-mode-literal-01 rf-python-library narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable
narrow-rf-injection-positive-01 plugin narrow treatment 3 100% 100% 0 207894 4338 15.7 33.0 0.0173 (est.) no
narrow-rf-injection-positive-01 plugin narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable
narrow-rf-mcp-execute-01 plugin narrow treatment 3 100% 100% 0 482597 4031 18.3 (2 at limit) 43.7 0.0228 (est.) no
narrow-rf-mcp-execute-01 plugin narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable
narrow-setup-requests-01 rf-setup narrow treatment 3 100% 100% 0 266524 4988 18.7 51.3 0.0179 (est.) no
narrow-setup-requests-01 rf-setup narrow Δ T−B unavailable unavailable unavailable unavailable unavailable unavailable

Skills

Skill Tasks Treatment outcome pass Baseline outcome pass Δ outcome pass Δ input tok Δ turns Δ cost $
rf-language 5 100% – unavailable unavailable unavailable unavailable
rf-python-library 3 100% – unavailable unavailable unavailable unavailable
rf-setup 1 100% – unavailable unavailable unavailable unavailable

Skipped / errored checks

None.

Incomplete runs

None.

Process checks (treatment arm, not in deltas)

  • narrow-language-bdd-01 loaded_rf_language_skill: 3/3
  • narrow-language-data-driven-01 loaded_rf_language_skill: 3/3
  • narrow-language-embedded-01 loaded_rf_language_skill: 3/3
  • narrow-language-env-varfiles-01 loaded_rf_language_skill: 3/3
  • narrow-language-suite-setup-tags-01 loaded_rf_language_skill: 3/3
  • narrow-non-rf-control-01 bounded_tool_calls: 3/3
  • narrow-non-rf-control-01 no_libdoc_invocation: 3/3
  • narrow-non-rf-control-01 no_rf_mcp_calls: 3/3
  • narrow-python-library-flaky-listener-01 loaded_rf_python_library_skill: 3/3
  • narrow-python-library-inventory-scope-01 loaded_rf_python_library_skill: 3/3
  • narrow-python-library-mode-literal-01 loaded_rf_python_library_skill: 3/3
  • narrow-rf-injection-positive-01 leveraged_rf_lookup: 3/3
  • narrow-rf-mcp-execute-01 rf_mcp_before_write: 3/3
  • narrow-rf-mcp-execute-01 rf_mcp_called_at_least_twice: 3/3
  • narrow-rf-mcp-execute-01 rf_mcp_had_successful_result: 3/3
  • narrow-setup-requests-01 used_uv_add: 3/3

Adversarial tier (reported only, never gates)

None.

manykarim and others added 2 commits November 11, 2026 08:34
…imit

DeepSeek sometimes answers the one-turn "ok" probe with a tool call, so
claude stops at --max-turns 1 and exits 1 although the provider answered.
That failed the rf-web and rf-desktop Doctor steps in run 42887127109.
The ping now reads the final result event: error_max_turns counts as
verified auth, and a real failure reports the result's reason instead of
stderr, which only held the unrecognized_model notice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHEmzL3ErAhGQykKWDx2wP
rf-desktop's PlatynUI tasks stopped at the 1.5 turn limit in 3-5 of 6
sessions and their pass rate swung between 0.33 and 0.83 across the two
N=6 runs (42887127109, 42993062842). OPENROUTER_TURNS_FACTORS gives a
plugin its own factor (rf-desktop 2.0); each matrix leg carries it, so the
PR and full jobs and the stored baselines agree. Claude runs keep 1.0.
The new `plugins` input re-records one plugin without rerunning the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHEmzL3ErAhGQykKWDx2wP
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.

1 participant