ChessMoves AI

Admin ยท Model Strategy ๐Ÿ›ก ๐Ÿ“Š ๐Ÿ“‰ ๐Ÿ”—๐Ÿ“‹๐Ÿ’ท๐Ÿ”‘ ๐Ÿ‘ฅ ๐Ÿ“ก ๐Ÿง  ๐Ÿงฉ โ™Ÿ๏ธ๐Ÿงฉ ๐Ÿฆ ๐Ÿช ๐Ÿ“ˆ ๐Ÿค– ๐Ÿงฎ ๐Ÿงญ ๐Ÿ’ณ ๐Ÿ’ท ๐Ÿ“ฐ ๐Ÿ’ผ

๐Ÿงฎ AI Strategy โ€” capability map, model economics & roadmap

What we actually have across every AI-powered surface, and the cost-benefit case for keeping self-hosted qwen2.5:3b (or a bigger self-hosted model) vs. switching to a metered frontier model. Usage figures on this page are read live from production โ€” they are measurements, not prose, and are fetched on every load. The pricing table and the argument built on it are still written by hand from published API rates; re-check those before acting if the review stamp below is old. See sources & assumptions at the bottom.

Loading live usageโ€ฆ

๐Ÿ—บ AI capability map โ€” what we actually have

Every AI-powered surface on the site, in one place. This site is an AI-first product โ€” the table below is the honest inventory behind that claim, not the pitch.

FeatureWhat it doesBackendStatusModel(s)
My SecondOpponent scouting: PGN ingestion, RAG-grounded prep briefs, opening/weakness Q&A chatPython ai-coach-servicelow usage โ€ฆqwen2.5:3b (floor) โ†’ Groq/Gemini/Claude (admin-only lever)
AI CoachPer-game Stockfish analysis + grounded mistake explanations (CCC recipe: concept extraction โ†’ LLM verbalizer โ†’ hallucination gate)Python ai-coach-service (same service as My Second โ€” they share the LLM, RAG store, and Stockfish pool)live โ€ฆSame as above
Coach (legacy)Per-game analysis, a separate persistent weakness profile (weakness_profiles table), targeted drillsNode-native โ€” its own game_analyses table, no Python involvednear-dead โ€ฆNone โ€” fully rule-based/statistical, no LLM anywhere in this path
Maia-2Human-move prediction, P(move | rating) โ€” "62% of players at your level find this move"maia2-service containerlive โ€” My Second prep, Coach's /maia, and AI Coach's own pipeline (_enrich_with_maia)Own CNN, not an LLM
Model routerAdmin-only frontier-model fallback for chat + verbalizer: tries each configured provider, falls through on real failure not just absenceai-coach-service/model_router.pyfragile โ€” Groq WAF-blocked and Claude out-of-credit both found live this session (Groq fixed; Claude needs billing)Groq Llama 3.3 70B โ†’ Gemini โ†’ Claude Haiku 4.5

The thing most worth your attention here: Coach and AI Coach are two independently-built per-game analysis systems, live at the same time, linked separately in the main nav, writing to two unrelated database tables (game_analyses vs ai_coach_analyses). That may be intentional (Coach = fast/free rule-based first look, AI Coach = deeper grounded-LLM analysis) โ€” but nothing in the codebase states that as a decision, and a new user landing on either one has no way to know the other exists or why they'd pick one over the other. Worth an explicit call: merge the weakness-profile/drill machinery into AI Coach (it's the more capable pipeline and already has a "no persistent loop" gap โ€” see below โ€” that Coach's weakness_profiles table already half-solves), or clearly differentiate the two in the product itself.

Cross-cutting tasks & challenges (AI Coach-specific detail, diagrams and the grounding-gate design: module health โ†’):

2026-08-01 update, from actually wiring this up: the Claude test key turned out to have no credit balance (nothing to fix in code โ€” needs a payment method added at console.anthropic.com). Gemini's "no card needed" free tier turned out to be real only outside the EU/EEA/UK/Switzerland โ€” an EU-based Google account gets a hard-coded 0 free-tier quota until billing is enabled, confirmed directly against Google's API with two separately-issued keys. Groq was added as a third provider โ€” genuinely free at every account region, no billing wall, hosting Llama 3.3 70B โ€” and model_router.py now tries each configured provider in priority order (Groq โ†’ Gemini โ†’ Claude) and falls through to the next on any failure, not just when a key is blank, since a configured-but-broken provider (exactly what happened here) would otherwise silently block every request forever. The pricing/volume analysis below is unaffected โ€” it's about $/token economics at scale, not which specific provider unblocks testing today.
Capability map TL;DR Current state Model pricing Cost per interaction Cost at scale Caps needed Self-host a bigger model? Verdict
TL;DR

Switch the LLM calls to Claude Haiku 4.5 (or Gemini 3 Flash as a cheaper alternative) โ€” at every realistic scale up to 10,000 users, the LLM bill stays under ~5% of subscription revenue even in a heavy-usage scenario, and under ~1% in a typical one. The 3B model wasn't the actual bug this session โ€” bad, inconsistent input data was (see the two fixes shipped today). A bigger brain wouldn't have fixed the indexing mismatch; grounding the data did. But a frontier model would follow the anti-hallucination system prompt more reliably and stop needing a rule-based safety net to catch its mistakes.

One prerequisite before flipping the switch: My Second chat currently has no monthly message cap โ€” only a 10-req/min rate limiter. That's free at $0 marginal cost today; it is not free once every message has a per-token price. A single very active "unlimited" Premium user could plausibly cost more in API fees than their $9.99/mo subscription. Add a per-tier monthly chat cap first (numbers below).

Don't build a GPU box for a bigger local model yet. The breakeven vs. metered Haiku is roughly ~50,000 chat messages/month sustained โ€” around 10,000 users all chatting heavily. Below that, metered API is cheaper, has no fixed cost, and doesn't compete with Stockfish for CPU. Revisit once real usage data says you're near that line.

$0
Real subscription revenue today
Stripe isn't live in prod. 0 stripe_customer_id rows. All non-free tiers in the DB were seeded, not purchased.
34
Total users
26 standard / 7 premium / 1 free โ€” all seeded test accounts, not organic signups.
โ€ฆ
AI Coach analyses, last 30d
Read live. Concentrated in a handful of dogfooding accounts, not a representative subscriber base.
~1
My Second analyses, ever
The feature has essentially no real usage yet โ€” it shipped its stability fixes today.
~โ‚ฌ30/mo
Current total infra cost
Single Hetzner box (4 vCPU / 7.6 GB) runs everything: API, Postgres, Stockfish, and the local LLM.

Where things stand today

This whole analysis is a forward projection, not a report on live economics โ€” read every dollar figure below as "if you launch roughly as currently planned," not "what we're making." Two things make that explicit:

What is real: AI Coach is the only surface with sustained use, from dogfooding rather than organic demand โ€” see the live figure above. (Corrected 2026-08-25: this paragraph previously cited "2,037 analyses in the last 30 days". That was the ALL-TIME row count mislabelled as a monthly rate; the true 30-day figure is roughly two orders of magnitude smaller.) My Second has almost none yet, because today's fixes are what make it usable enough to actually put in front of people. So the AI Coach numbers below are closer to "current," and the My Second numbers are closer to "if this launches and gets adopted."

Model pricing landscape (Aug 2026)

Per 1M tokens, list price. The self-hosted row has $0 marginal cost but isn't really free โ€” see the caveats underneath.

ModelTypeInput $/MOutput $/MNotes
qwen2.5:3b-instruct (current)Self-hosted, CPU$0$0Shares the API box's 4 vCPU with Stockfish; ~18 tok/s; one LLM job at a time in the verbalizer
Gemini 3 Flash PreviewFrontier, budget$0.25$1.50Cheapest metered option surveyed
GPT-5.4 MiniFrontier, budget$0.75$4.50
Claude Haiku 4.5Frontier, budget$1.00$5.00Recommended default below
Gemini 3.6 FlashFrontier, mid$1.50$7.50Newest Flash, launched Jul 2026
Claude Sonnet 5Frontier, mid$3.00*$15.00**$2/$10 intro pricing through 2026-08-31, then standard rate
Claude Opus 5Frontier, flagship$5.00$25.00
Claude Fable 5Frontier, highest-capability$10.00$50.00Overkill for this use case

What "self-hosted is free" actually hides: qwen2.5:3b's marginal cost per call is $0, but its fixed cost is the ~โ‚ฌ30/mo box, shared with Stockfish and everything else. The real cost of the current setup isn't dollars โ€” it's a throughput ceiling (single-threaded generation, ~18 tok/s) and a weak instruction-following track record (documented in today's bug fixes). Frontier pricing above is the dollar cost of removing both problems.

What each feature actually costs per interaction

Token estimates from reading the actual prompt-building code, not guesses.

FeatureInput tokOutput tokGemini FlashHaiku 4.5Sonnet 5Opus 5
My Second brief generationโ€”โ€”$0 โ€” 100% templated, no LLM call (see today's fix: now grounded in real PGN examples instead)
My Second chat message~2,800~500$0.0015$0.0053$0.0159$0.0265
AI Coach mistake explanation
โ‰ค2 calls/analysis, capped 120 tok output each
~500~240$0.0005$0.0017$0.0051$0.0085

Every number here is a fraction of a cent. Per-call cost was never going to be the problem โ€” volume is, specifically uncapped chat volume. That's the whole ballgame below.

Cost at scale

Assumptions (replace with real numbers once Stripe is live and there's real usage telemetry): 15% of registered users convert to paid (65% Standard / 35% Premium split); of paying users, 35% actively use My Second in a given month; AI Coach usage averages 60% of each tier's monthly cap (Standard 2/mo, Premium 5/mo โ€” both far below the current beta's uncapped dogfooding rate). Two chat-intensity bands per active My Second user: light = 15 messages/mo (a couple of prep sessions), heavy = 150 messages/mo (chatting daily). User milestones mirror the existing infra scaling plan (SCALING.md).
UsersPayingRevenue/moScenarioHaiku 4.5 COGS% of revenueSonnet 5 COGS% of revenue
1,000
Launch
150$1,009Light$4.680.5%$14.041.4%
Heavy$42.614.2%$127.8112.7%
5,000
Growth
750$5,053Light$23.240.5%$69.731.4%
Heavy$211.424.2%$634.2612.6%
10,000
Scale
1,500$10,110Light$46.410.5%$139.211.4%
Heavy$422.054.2%$1,266.1312.5%

At Haiku pricing, LLM cost stays under 5% of revenue even in the heavy-chat scenario, at every milestone. At Sonnet pricing it's still net profitable, but starts eating a real slice of margin (~12.5% in the heavy case) โ€” Sonnet-class models are the ones worth reserving for a specific high-value call (e.g. a richer, occasional brief narration), not the default for every chat turn.

For context: at today's actual AI Coach volume (see the live figure above โ€” tens of analyses a month, all beta/free), switching that one feature to a frontier model costs well under $1/month across the entire pricing spectrum surveyed, Gemini Flash to Opus. The earlier version of this page put that band at $1โ€“17/month because it was working from a volume figure ~68x too high โ€” which means the conclusion was right and is now simply more so. There is no scenario where AI Coach's model choice is a financial decision at today's scale; it is a quality decision. The financial question only becomes real at the scale modelled below.

The prerequisite: per-user chat caps

Right now, hasSecondEntitlement() gates My Second chat by tier, not by message count โ€” once a user has any entitlement, chat is unlimited, rate-limited only to 10 requests/minute (aiRateLimit in shared.ts). That's a reasonable design when marginal cost is $0. It becomes a real liability the moment each message has a per-token price:

A single Premium user sustaining 3,000 chat messages/month (~100/day โ€” plausible for someone actively using the tool, not abuse) would cost $15.90/mo at Haiku pricing or $47.70/mo at Sonnet pricing โ€” against $9.99/mo of revenue from that user. The portfolio-level numbers above look fine because light users subsidize heavy ones, same as any SaaS โ€” but without a hard ceiling, one power user (or one compromised/scripted account) has uncapped downside.

Proposed caps (pick one model tier, then set the cap so worst-case COGS per user stays well under that tier's revenue):

TierSuggested capWorst-case cost @ HaikuWorst-case cost @ Sonnet
Free0 msg/moโ€”โ€”
Standard ($4.99/mo)100 msg/mo$0.53$1.59
Premium ($9.99/mo)400 msg/mo$2.12$6.36
Coach / Club1,000 msg/mo$5.30$15.90

400 msg/mo on Premium is generous โ€” over 13/day, far past what a real prep session needs โ€” while keeping worst-case COGS at Haiku pricing under 25% of that tier's revenue even if every single Premium user maxes it out every month (which none will in practice). This is a five-minute change: add a monthly counter next to the existing per-minute rate limiter, same pattern as consumeDaily in entitlements.ts.

Alternative: self-host a bigger model instead

The infra docs already priced this out (PRODUCTION.md): a dedicated GPU-attached box (CCX43 + GPU, for Ollama) runs ~โ‚ฌ250/mo (~$270) โ€” fixed, regardless of volume. A natural upgrade target is Qwen3-14B (same open family as today's 3B model, ~5x the parameters), which needs that GPU to run at usable latency โ€” the current CPU box already struggles at 3B (~18 tok/s, and the codebase notes qwen3:4b was tried and rejected for flooding the CPU with unsuppressable reasoning tokens).

PathFixed costMarginal costBreakeven vs. Haiku metered
Keep CPU box, swap to Haiku 4.5 API$0 extra~$0.0053/chat msgโ€”
Dedicated GPU box, self-host Qwen3-14B~$270/mo$0~51,000 chat msgs/mo

$270 รท $0.0053/msg โ‰ˆ 51,000 My Second chat messages/month to break even against metered Haiku. Cross- referencing the scale table above: only the 10,000-user heavy-usage scenario (78,750 msgs/mo) clears that bar. Every lighter scenario, and every milestone below 10,000 users, is cheaper on metered API.

Recommendation on this path: don't provision the GPU box speculatively. Ship on a metered frontier model now (zero fixed cost, scales down to zero if adoption is slow), and revisit self-hosting only once real telemetry shows sustained chat volume approaching ~50k messages/month โ€” at which point the GPU box also stops being "risk," and starts being an obviously-correct cost optimization on proven demand. This also sidesteps the current architecture's other real problem: the local LLM shares 4 vCPUs with Stockfish analysis, so heavier chat use today would slow down game analysis for everyone, not just cost money.

๐Ÿงช Trialling Anthropic against what we run today โ€” what is actually needed

Written 2026-09-05 from the code and the live ledger, not from a plan. Most of the machinery exists; one thing is missing, and it is the thing that makes a comparison possible at all.

The blocker. model_router.call_json() walks _PROVIDERS in a fixed order โ€” Groq, Gemini, Claude โ€” and returns on the first success. Claude is third, so while Groq answers, Claude is never called. The ledger proves it: 29 Groq calls and 13 local Ollama calls recorded, and 0 Anthropic calls ever โ€” despite ANTHROPIC_API_KEY being set in production. The key is live and the path is dead. Nothing can be compared until a provider can be pinned rather than raced.

What a trial needsStateWork
Pin a provider per requestExists Built 2026-09-09. AI_ADMIN_PROVIDER=claude narrows the chain to that one provider for both call_json and call_text. It NARROWS rather than reorders: “pinned first, then the rest” would fall through to Groq on any Claude failure and record that as the trial’s result, which is the failure being fixed. If the pinned provider is down the caller falls back to local Ollama and the ledger says plainly that Claude did not answer.
Same prompt answered by both, stored togetherMissing A paired-run mode writing both answers against one input id. Without it you are comparing different questions.
An objective quality metricExists eval_harness.gate_checks() โ€” the SAN grounding gate. Provider-independent and binary: did the model name a move that was not in the engine’s line?
A subjective quality metricExists eval_harness.run_rubric(), already wired to /admin-aicoach.html.
Per-call cost captureExists ai_usage records input/output tokens and cost_micros per call, per feature, per provider. Nothing to build.
A per-user cap before non-admins see itExists Built 2026-09-09. A hard monthly ceiling per account — admins $25, everyone else half their subscription — enforced in callClaude(), the one point every AI call in the Node API passes through. It had existed on a single route only, which left six other surfaces uncapped; a ceiling with six ways around it is not a ceiling.

Cost, from measured tokens rather than estimates. Averages over the calls actually in ai_usage, priced at Haiku 4.5’s $1/M input and $5/M output (claude_client.PRICE_PER_TOKEN_MICROS):

InteractionAvg inAvg outCost eachSample
AI Coach โ€” one mistake explained26191$0.0007224 calls
AI Coach โ€” one full run (~3 mistakes)783273$0.0021derived
My Second โ€” one chat message1,868140$0.00265 calls
News desk โ€” one draft600409$0.002613 calls

Small samples โ€” 5 to 24 calls each. The right order of magnitude, not settled figures; they firm up on their own as the ledger fills.

Feasibility against the caps we actually enforce. BETA_FREE_MONTHLY_CAP is 1: a free user gets one AI Coach run a month, which at Haiku prices costs $0.002 โ€” about a fifth of a cent. Two thousand free users would cost roughly $4 a month. The free tier is not the risk.

The exposure is Standard and above, which is uncapped (entitlements.isStandardTier returns gated: false). At $4.99/month a subscriber would have to send about 1,900 chat messages in a month to consume the subscription in Haiku costs alone. That is not a plausible human โ€” but it is entirely plausible for a script, and “unlimited” with a live API key behind it is the failure mode worth engineering against. The per-user cap is a prerequisite for cost control, not for margin.

What the trial would actually answer. Cost is close to settled โ€” the arithmetic above is not much in doubt. The open question is quality per dollar: Groq’s Llama 3.3 70B is free and currently wins every race, so the case for paying anything has to be that the gate rejects fewer Haiku drafts, or the rubric scores them higher, on the same inputs. That is one measurement, and it needs the pinning work above before it can be taken.

๐Ÿ— Where a paid model would sit

The routed path as it stands. Green is what shipped on 2026-09-09 to make the trial possible; dashed purple is the one piece still missing. Everything else solid is deployed and was before.

BrowserAI Coach ยท My Second Node APIentitlements ยท caps ai-coach-servicerag_pipeline ยท verbalizer model_router pinned provider, elsefirst that answers Groq ยท Llama 70Bfree ยท wins every race today GeminiEU billing wall Anthropic ยท Claudereachable when pinned Ollama ยท qwen2.5 3Blocal fallback ยท shares CPU SAN grounding gate concepts + verbalizer โ€”drops invented moves ai_usage ledgertokens ยท cost_micros BUILT ยท AI_ADMIN_PROVIDER narrows the chain to one NEEDED ยท paired run + comparison store same prompt, both providers, one input id

Solid = deployed ยท green = shipped 2026-09-09 ยท dashed purple = still missing. The gate sits AFTER the provider on purpose: it is the one guarantee that does not depend on which model answered, which is exactly what makes it the fair yardstick for comparing them.

๐Ÿงช How to actually run the trial, end to end

Nine steps. Two of them are the ones people get wrong: there are two provider switches for two services, and the ai-coach container is not rebuilt by scripts/deploy.sh.

  1. Check the key is live. ANTHROPIC_API_KEY must be set in the server .env. The AI cost page says so directly — if the trial panel reads “ANTHROPIC_API_KEY, which is not set”, stop here; everything below will silently fall back to Ollama.
  2. Set both switches. They are different services with separate routing, and the names are near-identical, which is exactly how someone sets one and believes they set both. In .env:
    AI_PROVIDER_ADMIN=anthropic   # Node API: analysis, recaps, Ask box, lessons
    AI_ADMIN_PROVIDER=claude      # ai-coach service: AI Coach, My Second
    Setting only the first trials roughly half the surfaces and the AI Coach numbers stay Groq’s.
  3. Deploy the API. bash scripts/deploy.sh — gated on CI, migrations run against the new image before the swap, previous image kept as chessflow/api:prev.
  4. Deploy the ai-coach container separately. deploy.sh rebuilds only the API — deliberately, it never touches the other containers. Without this step the Python pin is dead code and AI Coach keeps answering from Groq:
    cd /opt/chessflow-backend
    docker compose -f docker-compose.prod.yml build ai-coach-service
    docker compose -f docker-compose.prod.yml up -d --no-deps ai-coach-service
  5. Confirm the trial is actually on. Open ๐Ÿค– AI cost. The panel at the top must read Running — admins on claude-opus-5. If it says “Not running”, the API did not pick up the variable.
  6. Make it spend something. As an admin, exercise one surface per service — a game recap or an Ask question (Node), and an AI Coach run or a My Second chat message (Python). A single call each is enough to prove the route.
  7. Verify from the ledger, not from the feeling. Back on the AI cost page, the By provider & model table must now show an anthropic row with a non-zero cost per call. If every row still says ollama or groq, the request never reached Claude — that is the exact failure this trial exists to rule out, and it is invisible from the answers themselves.
  8. Watch the ceiling. The same panel tracks spend against $25/month. At the cap the gateway refuses further calls for that account across every AI surface, with the figure in the message. Raise AI_SPEND_ADMIN_CAP_USD if a longer run is wanted — deliberately a config change, not a silent overflow.
  9. Judge it on the gate, not on taste. eval_harness.gate_checks() is provider-independent and binary — did the model name a move that was not in the engine’s line? Compare that pass rate and the cost per call in the table above against the Groq baseline. run_rubric() is the subjective half.

To stop the trial: clear both variables and redeploy both services. Nothing else needs undoing — unset means every account follows AI_PROVIDER exactly as before.

What this still cannot tell you. There is no paired-run store yet (the one dashed box above), so the comparison is against Groq’s historical answers to different questions rather than both models answering the same prompt. That is enough to measure cost per call and gate pass rate; it is not enough to claim one model writes better prose than the other.

Verdict

Is this a viable business opportunity?
Yes, conditionally โ€” the unit economics work at every model tier surveyed, including the most expensive (Opus). The open question isn't cost, it's demand: 0 real subscribers today means this is unproven, not disproven.
Keep the current 3B model?
No โ€” it's not saving meaningful money (self-hosting is ~free either way at this scale), and it's the more fragile option: weaker instruction-following, slower, and shares CPU with Stockfish.
Upgrade to a bigger local model?
Not yet โ€” the GPU box only pays for itself past ~50k chat msgs/month. Revisit once usage data says you're near that line.
Switch to a frontier model?
Yes โ€” Claude Haiku 4.5 (or Gemini 3 Flash if cost is the only lever that matters) for the default chat/verbalizer calls. Reserve Sonnet 5+ for a specific, low-volume, high-value call if quality still falls short after grounding fixes.
What has to happen first?
Add a per-tier monthly chat-message cap (table above) before switching off $0-marginal-cost local inference โ€” this is the one change that actually gates financial risk.
Biggest unknown in this whole analysis?
Real conversion rate and real chat intensity per user โ€” both assumed, both need replacing with live data the moment Stripe goes live and people actually use My Second.

Sources & assumptions