openvibers/openvibe.ai AGENTS.md
AGENTS.md
| 1 | # AGENTS.md — OpenVibe.AI |
| 2 | |
| 3 | ## Project overview |
| 4 | |
| 5 | The AI authority of the OpenVibe network: providers, models, routing, versioned prompt templates and |
| 6 | workflows, runs, citations, scoped cache, quotas, free allowance, abuse limits, BYO keys and the public |
| 7 | AI Exchange API. Node.js/Express, PostgreSQL (`ov_ai`), CommonJS, **no build step**. Extracted from |
| 8 | OpenVibe.Live's AI subsystem; Live now runs with `AI_SERVICE=remote`. |
| 9 | |
| 10 | - Service id `ai`, port `4700`, public origin `https://ai.openvibe.services`, API host |
| 11 | `https://api.openvibe.exchange`, unit `openvibe-ai.service`. |
| 12 | - The reusable rule: a run returns a **draft/evidence package** (structured output + citations + |
| 13 | provenance) attributed to a workflow version, model and run id — never to a person. AI never owns |
| 14 | publication truth; Wiki, Blog, News, Live, Report and the rest decide what to publish. |
| 15 | |
| 16 | ## What it owns, and what it must not touch |
| 17 | |
| 18 | Owns the eleven record groups in `migrations/`: providers, models, routing profiles, prompt |
| 19 | templates, workflow definitions, runs, the request log (hashes, not prompts), citations, cache, |
| 20 | quotas/usage and audit. Also owns people's provider keys (`server/credentials.js`, |
| 21 | `server/vault.js`), people's own machines as providers (`server/machines.js`), free allowance |
| 22 | (`server/free-allowance.js`), tiers/budgets (`server/govern.js`, `server/run-budgets.js`), abuse |
| 23 | prevention (`server/abuse/`), the public price book and Exchange APIs (`server/api/exchange.js`, |
| 24 | `server/pricebook/`), GPU supply/provisioning (`server/gpu/`, `server/platform/supply.js`), |
| 25 | batches, deep dives, memory and the `ai.run.*` events. |
| 26 | |
| 27 | Does **not** own: publication (it drafts/evidences), the recordings it analyses (OpenVibe.Media), |
| 28 | identity (OpenVibe.Network), or the meaning of a product's prompt (each workflow's owner). |
| 29 | |
| 30 | ## Who calls it, and whom it calls |
| 31 | |
| 32 | - Callers: every first-party product, with an OpenVibe.Network **service token** audience |
| 33 | `openvibe.ai` and the route's capability (`ai.run.create`, `ai.workflow.manage`, `ai.app.run`, …). |
| 34 | Live (`AI_SERVICE=remote`), Network (footer site copy), Actor (model routing), Blog (devblog |
| 35 | agent), Exchange pages and developer apps are the current callers. |
| 36 | - Depends on: PostgreSQL 18 through `openvibe-sdk/db`; Network (`server/auth.js`: JWKS, service |
| 37 | tokens, SSO for the console); Events (`ai.run.queued|succeeded|failed|cached` outbox, and the |
| 38 | `bench.rating.changed` subscription); Bench (ratings/cost boards for routed evidence); Media |
| 39 | (recordings read over allow-listed https/signed URLs); Bot (`bot.machine.ai` for a person's own |
| 40 | machine); provider APIs, whisper.cpp and `openvibe-llm.service` on this host. |
| 41 | |
| 42 | ## Repository map |
| 43 | |
| 44 | - **Entry:** [server/index.js](server/index.js) — `start()` wires config, database, registry, provider |
| 45 | pool, cache, quotas, runs, engine, auth, subscriptions and telemetry; also what tests call. |
| 46 | - **App:** [server/app.js](server/app.js) — Express middleware, CSP headers, CORS for the public |
| 47 | reads, the `/api/v1`, `/internal`, `/you`, `/console` mounts, `/shared`, legal pages. |
| 48 | - **Config:** [server/config.js](server/config.js) — pure `load(env)`; secrets are only ever read by |
| 49 | **name** through `secretRef()`. |
| 50 | - **Database:** [server/db.js](server/db.js) opens/migrates `ov_ai`; schema in |
| 51 | [migrations/](migrations/) (currently `0001_initial.sql` … `0081_deep_dives.sql`). |
| 52 | - **Auth:** [server/auth.js](server/auth.js) — `openvibe-contracts` `serviceAuth` + capabilities; the |
| 53 | JWKS key comes from `openvibe-sdk/auth`. Person sessions in [server/person-session.js](server/person-session.js). |
| 54 | - **API:** [server/api/](server/api/) — `runs.js`, `admin.js`, `exchange.js`, `openai.js`, |
| 55 | `messages.js` (Anthropic-compatible), `models.js`, `credential.js`, `batches.js`, `actions.js`. |
| 56 | - **Workflows:** [server/workflows/engine.js](server/workflows/engine.js), definitions and seed in |
| 57 | [server/workflows/](server/workflows/). |
| 58 | - **Providers:** [server/providers/](server/providers/) — adapters, pool, circuits, fallbacks. |
| 59 | - **Registry/run state:** [server/registry.js](server/registry.js), [server/runs.js](server/runs.js), |
| 60 | [server/cache.js](server/cache.js), [server/quota.js](server/quota.js). |
| 61 | - **Public pages:** [server/home.js](server/home.js), [server/openapi.js](server/openapi.js); |
| 62 | server-rendered run receipts in [server/run-pages.js](server/run-pages.js). |
| 63 | - **Console:** [server/console/](server/console/) — operator console, Network staff only. |
| 64 | - **Capture worker:** [server/capture/worker.js](server/capture/worker.js) — headless Chrome for deep |
| 65 | dives, separate unit `openvibe-ai-capture.service` on `127.0.0.1:4711`. |
| 66 | |
| 67 | ## Commands |
| 68 | |
| 69 | ```bash |
| 70 | npm install # or npm ci |
| 71 | cp .env.example .env |
| 72 | npm run dev # NODE_ENV=development, http://localhost:4700 |
| 73 | npm start # node server/index.js |
| 74 | npm test # every test/*.test.js |
| 75 | npm test -- quota cache # only files whose name contains one of the words |
| 76 | ov test # the harness's sharded run of what the change reaches |
| 77 | node --check <file.js> # syntax check; no linter/formatter configured |
| 78 | ``` |
| 79 | |
| 80 | Without a provider key everything falls back to the deterministic stub provider, so no test needs the |
| 81 | network or a paid key. |
| 82 | |
| 83 | ## Database and migrations |
| 84 | |
| 85 | PostgreSQL database `ov_ai` on the host's data role (ADR-035). Without `DATABASE_URL`, development and |
| 86 | tests use embedded PGlite. Every read/write is async through `openvibe-sdk/db`; an INSERT that needs |
| 87 | its id uses `RETURNING id`. Migrations are `migrations/NNNN_name.sql`, applied at boot; **never edit a |
| 88 | migration that has run** — `ov_migrations` records its checksum (see |
| 89 | [docs/cutover-pr-openvibe-ai-15.md](docs/cutover-pr-openvibe-ai-15.md)). Add a new number instead. |
| 90 | Migrations only add tables and columns. |
| 91 | |
| 92 | ## Auth and capabilities |
| 93 | |
| 94 | Every API route needs a Network service token for audience `openvibe.ai`, the route's capability and, |
| 95 | for runs, a namespace the token's `ns` claim allows (namespaces fail closed). Capability ids are |
| 96 | decided by the **pinned** `openvibe-contracts` release: an unknown id is refused, never allowed. The |
| 97 | operator console is Network staff only and needs `AI_CONSOLE_SESSION_SECRET` and |
| 98 | `OV_OAUTH_CLIENT_SECRET`; person routes under `/api/v1/me/*` accept only the person's own token or |
| 99 | AI's same-origin session — never a service token or `on_behalf_of`. Developer-app tokens |
| 100 | (`sub app:app_<ULID>`) are accepted **only** on `ai.app.run` routes and act as their project. |
| 101 | |
| 102 | ## HTTP surface (short) |
| 103 | |
| 104 | - Service-to-service: `POST /api/v1/runs`, `GET /api/v1/runs/:id`, workflows, providers, models, |
| 105 | credentials, quotas, usage and audit under `/api/v1/*`. |
| 106 | - Developer apps: `POST /api/v1/{chat,generate,summarize,classify,extract,embed}` and |
| 107 | `GET /api/v1/runs/:id`, rate-limited per address on the public vhost. |
| 108 | - Exchange: the OpenAI-compatible `POST /v1/chat/completions` and Anthropic-compatible |
| 109 | `POST /v1/messages`, model lists, read APIs and `/openapi.json`, served on |
| 110 | `api.openvibe.exchange`. |
| 111 | - Health: `/api/health`, `/api/ready`; `/metrics` for direct loopback callers only. |
| 112 | |
| 113 | ## Frontend loading model |
| 114 | |
| 115 | There is **no SPA and no build step**. Human pages are server-rendered strings with the console's |
| 116 | classes and CSP (no external script on run receipts); the public human pages moved to |
| 117 | OpenVibe.Exchange in 2026-11-07 and this service 301s them by name. `/shared/*` is served from the |
| 118 | pinned `openvibe-shared` package in `node_modules` by `openvibe-shared/serve`. Never edit |
| 119 | `node_modules`; change the package and bump the pinned release. |
| 120 | |
| 121 | ## Deploy |
| 122 | |
| 123 | `sudo ovhost deploy ai` (strategy `git-checkout`): fetch, fast-forward `/opt/openvibe.ai`, install on |
| 124 | a lockfile change, restart, wait for `/api/ready`, roll back automatically on failure. Unit |
| 125 | `openvibe-ai.service`, env `/etc/openvibe/ai.env` (0600), vhost |
| 126 | [deploy/nginx/ai.openvibe.services.conf](deploy/nginx/ai.openvibe.services.conf). The release migrates |
| 127 | `ov_ai` at boot. `sudo ovhost rollback ai --to <sha>`; `sudo ovhost drill ai` for a restore drill. |
| 128 | Never put a secret in a command line, a file or a log. |
| 129 | |
| 130 | ## Tests |
| 131 | |
| 132 | `npm test` runs `test/run.js`, which executes each `test/*.test.js` in its own process through |
| 133 | `openvibe-shared/test-runner` (PGlite by default; `npm run test:pg` uses PostgreSQL). Local servers on |
| 134 | random ports, generated signing keys, the stub provider. A test that cannot run prints |
| 135 | `<label>: skipped (<why>)` and is not counted as passed; `-- --strict` makes a skip fail. Read |
| 136 | `test/README`-style guarantees in the README's "Acceptance" table: every guarantee names its test. |
| 137 | `ov test` is the harness wrapper for the same suite. |
| 138 | |
| 139 | ## Conventions |
| 140 | |
| 141 | - CommonJS (`require`/`module.exports`), 4-space indent, single quotes, semicolons. |
| 142 | - `camelCase` JS, `snake_case` PostgreSQL columns/tables. |
| 143 | - Errors are RFC 9457 `application/problem+json` from `openvibe-contracts` `http`; never return raw |
| 144 | provider errors or secrets. |
| 145 | - The request log keeps hashes, not prompts; raw prompts and inline images are not stored; cache |
| 146 | entries never cross requester/actor/target/attribution scope. |
| 147 | - `openvibe-contracts`, `openvibe-sdk` and `openvibe-shared` are pinned release tarballs in |
| 148 | `package.json`; bump by changing the tag, never by editing installed files. |
| 149 | - CORS: exact origins for `/stats` and `/release.json`, `*` only for the public creation catalogue; |
| 150 | token and console routes never use that list. |
| 151 | |
| 152 | ## Key pitfalls |
| 153 | |
| 154 | - **Capabilities come from the pinned contracts release.** A feature that adds a capability must bump |
| 155 | `openvibe-contracts` first, or the route refuses every caller. |
| 156 | - **Namespaces fail closed.** A service token without `ns` runs nothing; do not add a fallback. |
| 157 | - **The public pages are Exchange's.** Do not re-grow HTML here; redirect instead. |
| 158 | - **Never edit an applied migration** (checksum recorded) and never create a table at runtime. |
| 159 | - **Secrets by name only.** `env:NAME` references, AES-256-GCM for people's keys with |
| 160 | `AI_CREDENTIALS_KEY`; a response or log must never carry a value. |
| 161 | - **Egress is allow-listed.** Media is fetched only from approved OpenVibe https hosts with every DNS |
| 162 | answer and redirect hop re-checked (`test/ssrf.test.js`). |
| 163 | - **Quotas refuse before any provider call**, with 429 + `Retry-After`; abuse limits and the free |
| 164 | allowance run before metered capacity. |
| 165 | - **`/metrics` is host-local.** Do not expose it on the public vhost. |
| 166 | - **The capture worker is separate on purpose** (memory cap, one browser); a page that eats memory |
| 167 | must not take the API down. |
| 168 | - **Docs drift:** the README's "Depends on" version list can lag `package.json`; `package.json` is |
| 169 | what runs. |
| 170 | |
| 171 | ## Documentation |
| 172 | |
| 173 | - [README.md](README.md) — the full product, API and acceptance record. |
| 174 | - [docs/migration.md](docs/migration.md) — cutover from Live and the `ov_ai` layout. |
| 175 | - [docs/abuse-prevention.md](docs/abuse-prevention.md) — trust tiers, device cookie, proof of work. |
| 176 | - [docs/provider-matrix.md](docs/provider-matrix.md) — what may be resold and what is UNVERIFIED. |
| 177 | - [docs/provider-adapters.md](docs/provider-adapters.md), [docs/provider-registry.md](docs/provider-registry.md). |
| 178 | - [docs/ai-exchange-baseline.md](docs/ai-exchange-baseline.md), [docs/exchange-platform.md](docs/exchange-platform.md). |
| 179 | - [docs/memory-m5.md](docs/memory-m5.md), [docs/private-ai.md](docs/private-ai.md), |
| 180 | [docs/measured-metrics.md](docs/measured-metrics.md), [docs/deep-dive.md](docs/deep-dive.md). |
| 181 | - Contracts live in OpenVibe.Contracts; the service's own schemes are documented in `README.md`. |
| 182 |