diff --git a/bin/agentic-runner.mjs b/bin/agentic-runner.mjs index 6e6e6c9..2217e0b 100644 --- a/bin/agentic-runner.mjs +++ b/bin/agentic-runner.mjs @@ -61,7 +61,7 @@ const buildId = args['build-id'] || ''; // compete on specs for cents; a cheap judge ranks them) — so the implementation agent runs on // the low-cost tier. All-Fable implementation measured $7-9/page; tournament + Sonnet executor // targets $2.50-4.00 at the same gate bar. Both IDs verified live via GET /v1/models 2026-07-13. -const model = args.model || 'claude-sonnet-5'; +const model = args.model || 'claude-sonnet-5-5'; // Which endpoint + billing lane runs the executor (`claude -p`). Default: first-party Anthropic. // The credential preflight may FAIL THIS OVER to OpenRouter's Anthropic-compatible endpoint // (their "Anthropic Skin" — same executor model, billed to the owner's OpenRouter credits) when @@ -271,7 +271,7 @@ async function probeCred(label, candidates, url, headers) { // completion (which also validates the model slug + credits in one shot, loudly). if (!a.ok && a.usageLimited) { const orCands = credCandidates('OPENROUTER_API_KEY'); - const orModel = process.env.OPENROUTER_ANTHROPIC_MODEL || 'anthropic/claude-sonnet-5'; + const orModel = process.env.OPENROUTER_ANTHROPIC_MODEL || 'anthropic/claude-sonnet-5.5'; for (const cand of orCands) { try { const r = await fetch('https://openrouter.ai/api/v1/messages', { diff --git a/bin/explainmyrepo.mjs b/bin/explainmyrepo.mjs index 2a3bc6d..fd319f3 100755 --- a/bin/explainmyrepo.mjs +++ b/bin/explainmyrepo.mjs @@ -34,7 +34,7 @@ ARGUMENTS OPTIONS --out build directory (default: ./explainer-builds/) - --model Anthropic model for the brain steps (default: claude-sonnet-5) + --model Anthropic model for the brain steps (default: claude-sonnet-5-5) --no-deploy skip the deploy station (build + grade locally only) --no-publish skip publish-repo + repo-seo (no GitHub explainer repo) --no-notify skip the email notify station diff --git a/src/claude.mjs b/src/claude.mjs index 049332f..6b31284 100644 --- a/src/claude.mjs +++ b/src/claude.mjs @@ -31,7 +31,9 @@ const ANTHROPIC_VERSION = '2023-06-01'; // tonight's model-freshness sweep checked OpenAI's family and missed our own Anthropic default). // Sonnet is the authoring default: strong judgment for concept/content at a sane cost. Override // with --model or EXPLAINMYREPO_MODEL / ANTHROPIC_MODEL. -export const DEFAULT_MODEL = 'claude-sonnet-5'; +// 2026-10-13: bumped sonnet-5 -> sonnet-5-5 (created 2026-09-28). Live 1-token probe returned HTTP 200 with +// model echoed; the no-temperature rule above still applies. +export const DEFAULT_MODEL = 'claude-sonnet-5-5'; // ── THE AUTHORING MODEL (2026-08-08) ──────────────────────────────────────────────────────────── // Measured, not assumed. A blind A/B on the REAL concept-authoring task (identical brief, labels @@ -51,7 +53,11 @@ export const DEFAULT_MODEL = 'claude-sonnet-5'; // // HONEST LIMIT: n=1 per model, one repo, one judge. The 15x cost gap survives that; the 95-vs-89 // quality gap may not. Hence the automatic Anthropic fallback below rather than a hard switch. -export const AUTHORING_MODEL = 'z-ai/glm-5.2'; +// 2026-10-13 re-measure (blind, 2 repos, 4 judge passes each, same concept prompt): glm-5.2 scored 79/61 +// (clearly behind), glm-5.3 scored 91/94 vs claude-sonnet-5-5 96/93 at near-identical $/concept +// (glm spends its budget on hidden reasoning; 2-3x slower). So glm-5.3 replaces 5.2 as the OpenRouter +// lane's model, but it is NOT the default whenever a direct Anthropic key exists (see resolveModel). +export const AUTHORING_MODEL = 'z-ai/glm-5.3'; const OPENROUTER_URL = 'https://openrouter.ai/api/v1/chat/completions'; // An OpenRouter model id is namespaced ("z-ai/glm-5.2"); a first-party Anthropic id is not. @@ -61,9 +67,14 @@ export function resolveModel(env = {}, override) { if (override) return override; if (env.EXPLAINMYREPO_MODEL) return env.EXPLAINMYREPO_MODEL; if (env.ANTHROPIC_MODEL) return env.ANTHROPIC_MODEL; - // Prefer the measured champion when an OpenRouter key exists; otherwise the Anthropic default. - // Set EXPLAINMYREPO_AUTHORING=anthropic to pin the old behaviour without removing the key. - if (env.OPENROUTER_API_KEY && env.EXPLAINMYREPO_AUTHORING !== 'anthropic') return AUTHORING_MODEL; + // Owner directive 2026-10-13: local runs bill the owner's own Anthropic developer account directly, + // never via OpenRouter when a direct key exists (a pass-through reseller is never cheaper than the + // source). The OpenRouter authoring champion is now used only when it is the ONLY credential + // (nothing else could run) or when explicitly opted into with EXPLAINMYREPO_AUTHORING=openrouter. + // EXPLAINMYREPO_AUTHORING=anthropic still pins Anthropic unconditionally. + const hasAnthropicKey = Boolean(env.ANTHROPIC_API_KEY || env.CLAUDE_API_KEY); + const a = env.EXPLAINMYREPO_AUTHORING; + if (env.OPENROUTER_API_KEY && a !== 'anthropic' && (a === 'openrouter' || !hasAnthropicKey)) return AUTHORING_MODEL; return DEFAULT_MODEL; } @@ -301,7 +312,8 @@ async function callClaudeOnce({ apiKey, model, system, user, maxTokens, temperat const j = await resp.json(); // Anthropic does not return a charge, so this lane is DERIVED from published per-token rates and // is marked as such in the receipt. Only the OpenRouter lane is a measured figure. - const RATES = { 'claude-sonnet-5': [2, 10], 'claude-opus-5': [5, 25], 'claude-haiku-4.5': [1, 5] }; + // sonnet-5-5 rate is the published list price as reported by third-party sources, not read from a bill. + const RATES = { 'claude-sonnet-5-5': [2, 10], 'claude-sonnet-5': [2, 10], 'claude-opus-5-5': [4, 20], 'claude-fable-5-1': [10, 50], 'claude-opus-5': [5, 25], 'claude-haiku-5-5': [0.1, 0.5], 'claude-haiku-4.5': [1, 5] }; const [rin, rout] = RATES[model] || [0, 0]; recordSpend(model, ((j.usage?.input_tokens || 0) * rin + (j.usage?.output_tokens || 0) * rout) / 1e6, { in: j.usage?.input_tokens, out: j.usage?.output_tokens }); diff --git a/tests/brain-lane.test.mjs b/tests/brain-lane.test.mjs index 062bc34..6cfec2b 100644 --- a/tests/brain-lane.test.mjs +++ b/tests/brain-lane.test.mjs @@ -75,7 +75,16 @@ import { resolveModel, isOpenRouterModel, AUTHORING_MODEL, DEFAULT_MODEL } from test('authoring — an OpenRouter key selects the measured champion', () => { assert.equal(resolveModel({ OPENROUTER_API_KEY: 'x' }), AUTHORING_MODEL); - assert.equal(AUTHORING_MODEL, 'z-ai/glm-5.2'); + assert.equal(AUTHORING_MODEL, 'z-ai/glm-5.3'); +}); + +test('authoring — a direct Anthropic key outranks OpenRouter (owner directive 2026-10-13)', () => { + assert.equal(resolveModel({ OPENROUTER_API_KEY: 'x', CLAUDE_API_KEY: 'k' }), DEFAULT_MODEL); + assert.equal(resolveModel({ OPENROUTER_API_KEY: 'x', ANTHROPIC_API_KEY: 'k' }), DEFAULT_MODEL); +}); + +test('authoring — EXPLAINMYREPO_AUTHORING=openrouter is the explicit opt-in even with an Anthropic key', () => { + assert.equal(resolveModel({ OPENROUTER_API_KEY: 'x', CLAUDE_API_KEY: 'k', EXPLAINMYREPO_AUTHORING: 'openrouter' }), AUTHORING_MODEL); }); test('authoring — NO OpenRouter key leaves the old behaviour exactly as it was', () => { diff --git a/tools/concept-tournament.mjs b/tools/concept-tournament.mjs index 567b742..54f1d99 100644 --- a/tools/concept-tournament.mjs +++ b/tools/concept-tournament.mjs @@ -110,8 +110,8 @@ function parseSpec(text, model) { } const CANDIDATES = [ - { model: 'claude-sonnet-5', call: callAnthropic, needs: anthropicKey }, - { model: 'claude-fable-5', call: callAnthropic, needs: anthropicKey }, + { model: 'claude-sonnet-5-5', call: callAnthropic, needs: anthropicKey }, + { model: 'claude-fable-5-1', call: callAnthropic, needs: anthropicKey }, { model: 'gpt-5.6-sol', call: callOpenAI, needs: openaiKey }, ]; const JUDGE = { model: 'gpt-5.6-terra', call: callOpenAI, needs: openaiKey };