openvibers/openvibe.compare AGENTS.md
AGENTS.md
AGENTS.md — OpenVibe.Compare
Project overview
Public, source-linked comparisons for the OpenVibe network: AI models (price and measured Bench
quality/speed), AI coding agents (Codes harnesses plus verified Bench runs), OpenVibe plans
(Billing's public plan list) and compute prices (Exchange's reviewed GPU/computer tariffs).
Node.js/Express, fed entirely by other services' public APIs, no database, no accounts, no write
API. Server-rendered pages; openvibe-shared for the shell.
- Origin
https://openvibe.compare, port5280, unitopenvibe-compare.service. - One rule above all: missing evidence is not zero. A thing without a measurement is unranked,
never scored 0; a failed feed shows an explicit unavailable state and fills nothing in.
What it owns
GET /— the comparison index and search.GET /comparisons/:slug— one comparison; weight overrides via?w.<criterion>=.GET /things/:id— one thing across every comparison that contains it; an AI model's thing page
and/models/:org/:model301 to/ai/models/:model.GET /compute— compute prices from Exchange: filters, sorts and a two/three-offer side-by-side.GET /ai,/ai/leaderboard,/ai/providers,/ai/models/:model,/ai/compare[/:a-vs-:b],
/ai/method,/ai/feed.xml,/ai/harnesses,/ai/matrix,/ai/:ref(a Bench task kind or
benchmark) and/ai/arena— Report's AI comparison pages, with Bench's boards through
openvibe-sdk/bench, the AI catalogue and Exchange market joined, and Report's measured first
token, tokens per second and success beside the prices./ai/harnessesdraws each harness's effect
against the neutralov-barewith its interval;/ai/matrixcrosses harness and model; an agent
pair (<harness>+<model>-vs-<harness>+<model>) splits its gap into model, harness and interaction.
/comparisons/ai-modelsand an AI model's/things/:id301 to the AI pages.GET /api/v1/comparisons[/:slug]andGET /api/v1/compute— the same data as JSON, CORS-allowed
for the origins in [server/origins.json](server/origins.json).robots.txt,sitemap.xml,llms.txt, Dataset JSON-LD, andAccept: text/markdownnegotiation —
all generated from the same data, never hand-written separately.
It owns no private data, no paid execution, no builder or version-history write (reserved in the
schema with no API in v1).
Who it reads
The feeds in [server/data.js](server/data.js) ENDPOINTS and [server/ai-data.js](server/ai-data.js),
and the compute book in [server/compute.js](server/compute.js) SOURCE:
| Feed | What |
|---|---|
| api.openvibe.exchange/api/v1/exchange/market | dated offers and checked prices (AI Exchange) |
| bench.openvibe.services/api/v1/boards/chat.arena | Arena Elo and median response time (OpenVibe.Bench) |
| bench.openvibe.services/api/v1/runs?kind=codes.patch / codes.ticket | verified Bench runs (OpenVibe.Bench) |
| openvibe.codes/api/v1/harnesses | coding harness catalog (OpenVibe.Codes) |
| billing.openvibe.network/api/v1/plans | published plans (OpenVibe.Billing) |
| openvibe.report/api/v1/ai/performance?group=model&days=30 | measured AI time to first token, tokens per second and success rate, public model rows with a 20-call floor (OpenVibe.Report) |
| openvibe.exchange/api/compute/prices | reviewed compute tariffs per provider and region (OpenVibe.Exchange) |
| bench.openvibe.services/api/v1/{kinds,ratings,benchmarks,rankings,runs,overview} | AI boards, benchmarks and new runs through openvibe-sdk/bench (OpenVibe.Bench) |
| bench.openvibe.services/api/v1/factors?task_kind=&days= | model and harness apart: harness effects, agents and interactions, with intervals (OpenVibe.Bench) |
| api.openvibe.exchange/api/v1/exchange/{catalog,market} | AI catalogue and dated provider prices (OpenVibe.AI / Exchange) |
It does not call AI, Live, Network or any authenticated API. Nothing here writes anywhere.
Repository map
- Entry: [server/index.js](server/index.js) — listens on
PORT || 5280, graceful SIGTERM. - App/routes: [server/app.js](server/app.js) — security headers, static
public/,/shared,
health/ready,/release.json,/api/v1, pages, 404 and 503. - Data: [server/data.js](server/data.js) — fetches the seven feeds (8 s timeout; 3 s for Report), builds the three
comparisons, coalesces concurrent reads and caches for ten minutes; a failed feed becomesnull. - AI data: [server/ai-data.js](server/ai-data.js) joins the catalogue/market and Bench's boards with
Report's measured rows and reads Bench's factor fit for the harness board, the matrix and an agent
pair; [server/ai-views.js](server/ai-views.js) renders the Report pages, the Bench boards and the factor
pages. A failed source leaves a worded status and a dash, never a zero; pages cache for two minutes. - Compute: [server/compute.js](server/compute.js) normalizes Exchange's tariffs and derives the
month (730 h) and GPU GB-hour prices; [server/compute-views.js](server/compute-views.js) renders the
page, Markdown and Dataset JSON-LD. A failed feed is not cached. - Views: [server/views.js](server/views.js) — HTML, markdown, escaping, JSON-LD, the method text.
- Ranking: [public/rank.js](public/rank.js) — the one ranking implementation, required by both the
server views and the browser. - Browser: [public/comparison.js](public/comparison.js), [public/compare-account.js](public/compare-account.js),
[public/ai-factors.js](public/ai-factors.js) (an agent pair's gap split, drawn withOVMetrics.split),
[public/compare.css](public/compare.css). - Deploy: [deploy/systemd/openvibe-compare.service](deploy/systemd/openvibe-compare.service),
[deploy/nginx/openvibe.compare.conf](deploy/nginx/openvibe.compare.conf).
Commands
npm ci
npm start # node server/index.js, http://127.0.0.1:5280
npm test # node --test test/*.test.js
ov test # the harness's sharded run of what the change reaches
node --check <file.js>
There is no dev script and no build step: edit server/ or public/ and restart.
Testing
test/compare.test.js and test/compute.test.js use Node's built-in test runner with fixture feeds. It pins the rules that
matter: every measurement is finite, dated, sourced, and references a known thing and criterion;
missing evidence stays missing; exact model identity only; ranking handles direction, equal values,
zero/invalid weights and all-zero weights (unranked); pages and JSON agree. ov test runs the same
file and test/compute.test.js plus the repository checks the harness adds.
Conventions
- Compact, single-line JS is the house style here (
server/app.js,server/data.js); match the
file you edit rather than reformatting it. - CommonJS,
'use strict', 4 spaces in the newer data helpers, no linter. - All user-visible data is escaped with
views.esc; JSON is embedded withjson()(escapes<). - Reuse
openvibe-shared/kit,openvibe-shared/shellandopenvibe-shared/serveinstead of new
layout or asset code.
Data and provenance rules
- A result needs a finite number, a parseable date, and an
https:source URL; otherwise it is not
added ([server/data.js](server/data.js)result). - Prices: the cheapest dated offer by input + output, both from the same provider. Provider feedback
and observation dates are labelled; no price is estimated. - Arena quality: at least ten judged games and exact model identity (
r.model === id); no fuzzy
joins, because a fuzzy join silently compares a different model revision. - Measured AI columns: Report's public model rows over 30 days, matched by the full thing id or the
offer's provider model name. Report groups by model name across providers, so the values describe the
model, not one provider's serving; under 20 calls or a failed feed is a dash, never zero. - Coding agents: only
provenance === 'verified'runs; benchmark version and model form separate
criteria; newest verified run per harness/cohort. - Plans: packages and memberships have separate units and are never ranked together.
- Ranking: min–max within the comparison then a weighted mean on 0–100; equal values get 50;
criteria with no measurements are excluded; all-zero weights leave everything unranked.
Deploy
No database and no service credentials. The unit runs /opt/openvibe.compare/server/index.js on
127.0.0.1:5280, env /etc/openvibe/compare.env. The README's launch path predates ovhost:
register-compare.py registers port 5280 with a lifecycle override until a Contracts manifest is
released, then merge-deploy.sh and ov-vhost-install deploy/nginx/openvibe.compare.conf. Keep the
vhost and unit in sync with [deploy/](deploy/); a docs-only change needs no deploy.
Pitfalls
- Do not invent a write path. Builder and version history are deliberately absent from v1.
- Do not cache a failed feed into an old value. It is
nullfor that read; the next request
retries, and the comparison lists it as unavailable. - Do not hand-write
sitemap.xml,llms.txtor JSON-LD. They are generated fromgetData()and
getCompute()so they cannot drift from the pages. - Compute is read from Exchange, never copied. A tariff's checked date and review date are shown
as published; an offer pastexpires_atis marked, never presented as current. - Keep
public/rank.jsframework-free and dual-use (serverrequireand browser global); the
test loads it both ways. - CORS is an exact allow-list ([server/origins.json](server/origins.json)); no
*and no
credentials. Accept: text/markdownon/,/comparisons/:slug,/things/:idand/computeis part of
the product; keep it when touching the views.- Do not remove the 503 handler: an unexpected feed failure must answer explicitly, not 500.
Documentation
The [README.md](README.md) is the whole product brief (there is no docs/). Contracts, if a manifest
is added later, live in OpenVibe.Contracts.