# AGENTS.md — OpenVibe.Exchange ## Project overview "Every AI model, one account." Exchange is the network's AI front: every model OpenVibe can run with the live price, speed and uptime of every offer that serves it, one chat that puts up to three models side by side, the OpenAI- and Anthropic-compatible endpoint, community machines, the uncensored tier, companions and the AI arena. It **reads OpenVibe.AI** (catalog, aggregate usage, and the runs a chat makes for a person) and keeps only two things of its own: a person's chats and their 18+ confirmation. Node.js/ Express, PostgreSQL (PGlite in development), CommonJS, **no build step**. - Service id `exchange`, port `5150`, origin `https://openvibe.exchange`, unit `openvibe-exchange.service`, release root `/opt/openvibe.exchange`. - The v1 marketplace is retired: `/api/v1/*` answers `410`, and its tables stay as they are, read-only. Every old chat row remains for a one-time import into Chat. ## What it owns - The pages and their live numbers: `/` (every model, popular, cheapest, uncensored, community machines, endpoint), `/models`, `/m//` (price in/out/cached per million, p50/p95, uptime, data policy, Try it, curl and the SDK line), `/api` (endpoint, model ids, keys, aggregate usage), `/start`, `/why` (a real run's explain), `/faq`, `/sell` (Link machines and pools; what is **not** sold), `/uncensored`, `/ai`, `/status`. - A person's chats (`server/chat/store.js`) and their 18+ confirmation; companions, lorebooks, notes, memory and the Locker; the arena and compare surfaces; the deep-dive/analysis pages. - The one-time import contract: `GET /internal/exchange/chat/export` requires Chat's service token with `exchange.chat.export` and the authenticated person's `x-ov-subject`; it returns only that person's unsealed chats. - The `/chat` redirect: 301 to `https://openvibe.chat/vibe`, carrying `?models=` and mapping old `@vN` model ids. Model pages' Try it links go there directly. - Its own PostgreSQL state, migrations and account-data receipts. Not owned: the catalog/prices/usage and the model runs (AI), the models and provider keys (AI/Network), identity, chat itself (Chat), ratings (Bench), machines and pools (Network/Link), money (Billing), Media. ## What it reads (AI and Network) - `GET https://ai.openvibe.services/api/v1/exchange/catalog` — models grouped, with every live offer: price (rate cards), measured speed/uptime, `metadata.data_policy` where recorded. Prepaid capacity has no price (`free: true`); **BYOK and restricted capacity are never an offer**. - `GET .../api/v1/exchange/usage?days=30` — aggregate requests, tokens and spend by day/model/product (`usage_daily`; never a prompt, answer or person). - `POST .../api/v1/runs` with `workflow: ai.complete` and `on_behalf_of` — one chat turn or Try it, on the person's allowance, with Exchange's token (`ai.run.create`, namespace `ai.complete`). - `GET .../api/v1/community/offers` — live community offers; a pool says **how many** machines serve it, never which ones ("Community pool (N machines)"). - `GET https://openvibe.network/api/v1/machines` and `/machine-pools` — the person's machines, pools and poolable machines, with their Network token (names, free VRAM/RAM, local network key, approvals, readiness); `POST /api/v1/machine-pools` proposes, `.../:id/revoke` stops. Link measures links and fit; Exchange shows what Network carries and says "not measured yet" when it carries nothing. - AI unreachable is **said, not papered over**: the catalog read keeps the last good copy for five minutes and pages carry an honest notice; no number is invented. ## Repository map - **Entry:** [server/index.js](server/index.js) — `createApp()`, listen, graceful stop. - **App:** [server/app.js](server/app.js) — helmet/CSP, `/auth`, `/internal/events`, `/shared`, `/api/v1/arena`, `/api/v1/analysis`, `/api/v1/compare`, `/api/v1`, pages, machine endpoints. - **Reads:** [server/ai.js](server/ai.js), [server/network.js](server/network.js), [server/bench.js](server/bench.js), [server/compare.js](server/compare.js), [server/model-directory.js](server/model-directory.js), [server/model-search.js](server/model-search.js), [server/compute-prices.js](server/compute-prices.js), [server/catalog-history.js](server/catalog-history.js), [server/analysis.js](server/analysis.js). - **Person state:** [server/chat/store.js](server/chat/store.js), [server/companions/](server/companions/) (`store.js`, `group.js`, `voice.js`, `adult.js`, `memory.js`, `lorebook.js`, `locker.js`, `taking-notes.js`), [server/actor.js](server/actor.js), [server/sharing.js](server/sharing.js). - **Arena:** [server/arena/store.js](server/arena/store.js), `category.js`; HTTP in [server/http/arena.js](server/http/arena.js), `compare.js`, `compare-site.js`, `analysis.js`, `deep-dive.js`, `dashboard.js`, `search.js`. - **Pages/API:** [server/http/pages.js](server/http/pages.js), `api.js`, `console.js`, `companions.js`, `principal.js`, `router.js`, [server/openapi.js](server/openapi.js); [server/render/](server/render/). - **Config/db:** [server/config.js](server/config.js), [server/db.js](server/db.js). - **Deploy:** [deploy/systemd/openvibe-exchange.service](deploy/systemd/openvibe-exchange.service), [deploy/nginx/openvibe.exchange.conf](deploy/nginx/openvibe.exchange.conf). ## Commands ```bash npm ci npm start # development: PGlite in data/pglite, http://localhost:5150 npm test # test/run.js, PGlite, mock Network/AI npm test -- pages chat retirement # filter by word npm run test:pg # same on PostgreSQL (EXCHANGE_TEST_STORE=pg) npm run seed:proof # scripts/seed-proof.js ov test # the harness's sharded run of what the change reaches ``` `DATABASE_URL` serves, `DATABASE_DIRECT_URL` migrates; `OV_OAUTH_CLIENT_SECRET` makes sign-in and the AI client-credentials token work. See `.env.example`. ## Database and migrations Exchange's own PostgreSQL database (ADR-035; PGlite under `data/pglite` in development). Async through `openvibe-sdk/db`; migrations `migrations/NNNN_*.sql` applied at boot; never edit an applied migration, add a new number. **Exchange v1's tables are read-only**: nothing is written to them any more, and the old chat rows exist only for the one-time import. A new column for the current product goes in a new migration. ## Auth, privacy and the 18+ gate - People sign in through Network SSO (OAuth client `exchange`, PKCE). Exchange's AI calls use its own client-credentials token with `on_behalf_of` the person, so the run is theirs (allowance, receipt). - A page rendered for a signed-in person is `private, no-store`; a public page is `public, max-age=60`. Do not cache a signed-in page publicly. - The 18+ confirmation is on the **account**, not a cookie: `/uncensored` explains the abliterated models and the hard floor. Do not move the gate into client state. - The old-chat export is service-only: Chat's token (`exchange.chat.export`) plus `x-ov-subject`; it returns only that person's unsealed chats. Never expose it publicly. - Service callers need a Network service token audience `openvibe.exchange` and the route's capability; account export/deletion goes through `/internal/events`. ## Frontend loading model Every page is **server-rendered with `openvibe-shared/kit` and complete without JavaScript**; there is no SPA, bundler or transpiler. `openvibe-shared` supplies the Frame/kit/legal/SEO and `/shared/*` from the pinned node_modules. The pages negotiate cleanly for agents (JSON/llms) and their numbers come from the same AI reads. CSP is in [server/app.js](server/app.js). ## Deploy Unit [deploy/systemd/openvibe-exchange.service](deploy/systemd/openvibe-exchange.service) runs `/opt/openvibe.exchange/server/index.js` on `127.0.0.1:5150`, env `/etc/openvibe/exchange.env`; vhost [deploy/nginx/openvibe.exchange.conf](deploy/nginx/openvibe.exchange.conf). The README does not document an `ovhost deploy exchange` command — use the host's own service installer. Migrations run at boot; no secret in a command line, file or log. ## Tests `npm test` runs each `test/*.test.js` in its own process with PGlite and in-process Network/AI mocks. It pins the pages and their numbers, the catalog/usage reads and the "last good copy for five minutes" rule, the `/chat` redirect and old `@vN` mapping, the v1 retirement (`410`), the 18+ gate, companions/arena/ compare, the internal chat export (subject-scoped), account export/deletion and discovery. `ov test` runs the same. ## Conventions - CommonJS, 4-space indent, single quotes, semicolons; reads live in `server/ai.js`/`server/network.js`, person state in `server/*/store.js`, HTTP in `server/http/`. - A number shown to a person comes from AI or Network and carries its freshness; a missing number is said, never defaulted. Prices are per million tokens with the currency and the offer. - Errors are problem+json with stable codes; an AI/Network failure is a clear notice, not a 500 page. - The v1 surface is retired: do not revive `/api/v1` marketplace routes; keep the `410`. ## Pitfalls - **Never invent a price, a model or an uptime.** If AI does not answer, keep the last good catalog for at most five minutes and show the notice; a page with no data says so. - **BYOK and restricted capacity are not offers**, and prepaid capacity is `free: true`; do not list them as purchasable. - **A community pool never names its machines**, only the count. - **Person state is private:** chats, companions, notes and the 18+ flag are per subject; a signed-in page is `no-store`. The old-chat export is the only cross-service read and is subject-scoped by Chat. - **Do not write Exchange v1's tables.** They are the migration's archive; the old chat rows are for the one-time import only. - **`/chat` is a 301 to Chat/Vibe** with `?models=` and the old `@vN` mapping; do not rebuild a local chat page. - **Money is AI's/Billing's**; Exchange charges a turn to the person's allowance through AI, never a local ledger. - **No fabricated "selling compute"**: `/sell` says plainly what is not sold (personal plans, personal API keys); machines/pools are Network's truth. - **A unit description that says "trade what people make" is stale** (the remake is the AI front); do not use it as the product description. ## Documentation - [README.md](README.md) — pages, what it reads, money/free use, the retired v1, run. - [STATUS.json](STATUS.json) — the remake's state. - [docs/](docs/) — platform review, shared analysis, C2 notes and screenshots. - OpenVibe.Contracts holds `exchange.*`/`ai.*` shapes; AI and Network own their upstream APIs.