openvibers/openvibe.ai .env.example
.env.example
| 1 | # ═══════════════════════════════════════════════════════════════ |
| 2 | # ai.openvibe.services — OpenVibe.AI Environment Configuration |
| 3 | # The public home and API origin. BASE_URL defaults to it in production. |
| 4 | # Copy to .env and fill in values. In production this lives at /etc/openvibe/ai.env (0600). |
| 5 | # Secrets are read by NAME only (providers store 'env:NAME' references); nothing logs or returns them. |
| 6 | # ═══════════════════════════════════════════════════════════════ |
| 7 | |
| 8 | # Server |
| 9 | PORT=4700 |
| 10 | HOST=127.0.0.1 |
| 11 | NODE_ENV=development |
| 12 | BASE_URL=http://localhost:4700 |
| 13 | # The human site: the moved public pages answer 301 here (defaults to https://openvibe.exchange). The API, |
| 14 | # a run's receipt and the operator console stay on this origin. |
| 15 | #OV_EXCHANGE_URL=https://openvibe.exchange |
| 16 | # Exact browser origins allowed to read GET /stats and /release.json (and their GET preflight). |
| 17 | # Defaults to https://ai.openvibe.services,https://openvibe.services; no credentials or token/console CORS. |
| 18 | #AI_CORS_ORIGINS=https://ai.openvibe.services,https://openvibe.services |
| 19 | |
| 20 | # PostgreSQL (ADR-035), written by OpenVibe.Host roles/data/add-service.sh ai: the runtime role through PgBouncer, |
| 21 | # the owner on a direct connection (migrations), and Valkey (shared limit counters). |
| 22 | # Without DATABASE_URL, development uses an embedded PGlite database in data/pglite. |
| 23 | DATABASE_URL= |
| 24 | DATABASE_DIRECT_URL= |
| 25 | VALKEY_URL= |
| 26 | VALKEY_PREFIX= |
| 27 | |
| 28 | # Identity: OpenVibe.Network signs the service tokens callers present (audience openvibe.ai). |
| 29 | # The public key is fetched from <OV_NETWORK_INTERNAL_URL>/api/.well-known/jwks by openvibe-sdk/auth |
| 30 | # (fresh keys, the last good ones through an outage, backoff, rotation); OV_NETWORK_PUBLIC_KEY pins a PEM instead. |
| 31 | OV_NETWORK_URL=https://openvibe.network |
| 32 | OV_NETWORK_INTERNAL_URL=http://127.0.0.1:4000 |
| 33 | #OV_NETWORK_JWKS_URL=http://127.0.0.1:4000 |
| 34 | #OV_NETWORK_ISSUER=https://openvibe.network |
| 35 | # Namespaces fail closed: a token runs only the namespaces its `ns` claim (its Network grant) names; a token |
| 36 | # without `ns` runs nothing. (AI_NS_FALLBACK and AI_NS_REQUIRED were retired on 2026-09-28.) |
| 37 | #OV_NETWORK_PUBLIC_KEY= |
| 38 | |
| 39 | # ── Operator console (/console; README "Operator console") ── |
| 40 | # Network SSO as OAuth client `ai` (the client AI's service tokens use). The Network must list |
| 41 | # ${BASE_URL}/auth/callback (https://ai.openvibe.services/auth/callback) as a redirect URI of client `ai`. |
| 42 | # OV_OAUTH_CLIENT_SECRET is also what the events relay and the user modules authenticate with. |
| 43 | #OV_OAUTH_CLIENT_ID=ai |
| 44 | #OV_OAUTH_CLIENT_SECRET= |
| 45 | # Signs the sign-in flow cookie and keys the client-address hashes: at least 32 random characters |
| 46 | # (openssl rand -hex 32). Required in production: without it or the client secret the console answers 503. |
| 47 | #AI_CONSOLE_SESSION_SECRET= |
| 48 | # Absolute console session lifetime in minutes (5-720) |
| 49 | #AI_CONSOLE_SESSION_TTL_MIN=60 |
| 50 | # A person's run-receipt session lifetime in minutes (5-10080) |
| 51 | #AI_PERSON_SESSION_TTL_MIN=1440 |
| 52 | # Audience a Network user token must carry |
| 53 | #AI_SSO_AUDIENCE=openvibe.network |
| 54 | # ── Events (the ai.run.* outbox relay and the Bench ratings subscription) ── |
| 55 | # EVENTS_URL is where AI publishes its own events and ensures its subscription. AI_EVENTS_SECRET is the |
| 56 | # v2 signing secret Events hands back on that subscription (32+ characters; comma-separated during rotation); |
| 57 | # without it POST /internal/events is off. AI_EVENTS_SUBSCRIBE=0 skips creating the subscription (an |
| 58 | # operator/script may create topic bench.rating.changed → http://127.0.0.1:4700/internal/events). |
| 59 | #EVENTS_URL=http://127.0.0.1:4300 |
| 60 | #AI_EVENTS_SECRET= |
| 61 | #AI_EVENTS_SUBSCRIBE=on |
| 62 | # Where a sparse bench.rating.changed is filled from (boards, cost per solved). |
| 63 | #AI_BENCH_URL=http://127.0.0.1:5230 |
| 64 | #AI_BENCH_TIMEOUT_MS=15000 |
| 65 | |
| 66 | # ── Billing (plan T5 lane F) ── |
| 67 | # One platform.usage-sample@1 reading per accounted run, posted to billing.usage.record with AI's |
| 68 | # service token, so Network must grant client `ai` billing.usage.record on openvibe.billing. Readings |
| 69 | # are queued from the start. In production they are sent to colocated Billing at 127.0.0.1:4600 by |
| 70 | # default; OV_BILLING_INTERNAL_URL overrides the address, and USAGE_SAMPLES=off stops queueing them. |
| 71 | #OV_BILLING_INTERNAL_URL=http://127.0.0.1:4600 |
| 72 | #USAGE_SAMPLES=on |
| 73 | #USAGE_SAMPLES_INTERVAL_MS=2000 |
| 74 | |
| 75 | # ── Billing Balance (AI Exchange F, CHECKPOINT D2/D9/D10/D12), off by default ── |
| 76 | # A request on a paid managed route (the model catalog's provider_access OWN_DEPLOYMENT or AUTHORIZED_WHOLESALE) |
| 77 | # holds its worst-case cost before a provider is called, extends the hold while streaming and settles once. |
| 78 | # Network must grant client `ai` billing.credit.hold.{create,extend,settle,release} on openvibe.billing. An app |
| 79 | # holds against its project's Balance (subject app + project_id); a person against their own (no project_id); no |
| 80 | # network.project.read is needed. |
| 81 | # Reads OV_BILLING_INTERNAL_URL, OV_NETWORK_INTERNAL_URL and OV_OAUTH_CLIENT_SECRET above; a missing Billing refuses |
| 82 | # paid requests (503). A settle Billing could not take is retried from credit_settle_queue with backoff. |
| 83 | #AI_CREDITS=0 |
| 84 | #AI_CREDITS_TIMEOUT_MS=5000 |
| 85 | #AI_CREDITS_SETTLE_RETRY_MS=5000 |
| 86 | #AI_CREDITS_SETTLE_MAX_RETRY_MS=300000 |
| 87 | |
| 88 | # ── Tier budgets (server/govern.js, T6 step 8): openvibe-sdk/govern in Valkey (VALKEY_URL above), off by default ── |
| 89 | # Units ai-token and ai-usd per subject and per project; tiers free, paid, staff. See README "Tier budgets". |
| 90 | #AI_GOVERN_TIERS=0 |
| 91 | #AI_GOVERN_POLICY_JSON= |
| 92 | #AI_GOVERN_TIER_MAP= |
| 93 | #AI_GOVERN_DEFAULT_TIER=free |
| 94 | #AI_GOVERN_HOLD_TOKENS=4000 |
| 95 | #AI_GOVERN_HOLD_USD=0.02 |
| 96 | #AI_GOVERN_TIMEOUT_MS=1000 |
| 97 | |
| 98 | # ── A person's own provider keys (server/credentials.js, WS-O task 2) ── |
| 99 | # 64 hex characters (openssl rand -hex 32). Keys stored through PUT /api/v1/credentials/:subject are AES-256-GCM |
| 100 | # encrypted with it; unset turns the credentials routes and credentialed runs off (503). Changing it makes every |
| 101 | # stored key unreadable: the storing service then has to send them again. |
| 102 | #AI_CREDENTIALS_KEY= |
| 103 | |
| 104 | # ── AI owner settings (D11, server/settings.js; openvibe-shared/config) ── |
| 105 | # The ai.exchange and ai.provider_keys namespaces. OV_CONFIG_KEKS is "<id>:<base64 32-byte key>[,<id>:…]" and |
| 106 | # OV_CONFIG_KEK_CURRENT names the one to use; they come from the host's env file, never the database. With a KEK, |
| 107 | # secret values (each provider's ai.provider_keys "<provider>.key") are sealed envelopes in the config tables and a |
| 108 | # DB dump has no key; unset, the store still works and reports "sealed": false. At boot AI moves the old |
| 109 | # provider_secrets rows (AI_CREDENTIALS_KEY) into sealed "<provider>.key" values, bound to each provider's origin, |
| 110 | # and deletes them after a successful read-back. |
| 111 | #OV_CONFIG_KEKS= |
| 112 | #OV_CONFIG_KEK_CURRENT= |
| 113 | |
| 114 | # ── Memory (server/memory/, Memory M2a) ── |
| 115 | # 64 hex characters (openssl rand -hex 32). Memory item text and source references are AES-256-GCM sealed with it; |
| 116 | # the dedupe MAC is HKDF-SHA256 (info "memory-mac") of the same key. Unset turns /api/v1/memory off (503 |
| 117 | # memory.unavailable). A key of its own, so credentials and memory rotate separately. Do not set it in production until |
| 118 | # the Memory M2a review; changing it makes existing items unreadable. |
| 119 | #AI_MEMORY_KEY= |
| 120 | |
| 121 | # The store's own maintenance (Memory M2c): the loop walks owner by owner, with a per-run cap. It is |
| 122 | # off in test mode and off while a provider is in a drill, so a drill never spends the platform's budget. |
| 123 | #AI_MEMORY_JOBS=1 |
| 124 | #AI_MEMORY_JOB_INTERVAL_MS=900000 |
| 125 | #AI_MEMORY_JOB_MAX_OWNERS=10 |
| 126 | #AI_MEMORY_JOB_MAX_SCOPES=4 |
| 127 | #AI_MEMORY_JOB_MAX_EMBEDDINGS=100 |
| 128 | #AI_MEMORY_JOB_MAX_MODEL_CALLS=20 |
| 129 | #AI_MEMORY_JOB_MAX_PROFILES=10 |
| 130 | |
| 131 | # ── The shared provider — the same names as OpenVibe.Live's admin AI settings ── |
| 132 | # (ai_enabled, ai_provider, ai_base_url, ai_api_key, ai_model, ai_pricing_json, |
| 133 | # ai_input/output_cost_per_mtok, ai_max_cost_usd_per_day). |
| 134 | #AI_ENABLED=true |
| 135 | # anthropic | openai | openrouter | groq | ollama | (blank = OpenAI-shaped). Anthropic only when |
| 136 | # AI_BASE_URL is blank or anthropic.com; any other base URL is treated as OpenAI-compatible. |
| 137 | AI_PROVIDER= |
| 138 | #AI_BASE_URL=https://api.openai.com/v1 |
| 139 | AI_API_KEY= |
| 140 | # Blank = claude-sonnet-5 for anthropic, gpt-4o-mini otherwise (Live's defaults) |
| 141 | AI_MODEL= |
| 142 | #AI_EMBEDDING_MODEL=text-embedding-3-small |
| 143 | # Image generation (POST /api/v1/image, route default.image): off unless set. The shared provider's image model. |
| 144 | #AI_IMAGE_MODEL=gpt-image-1 |
| 145 | #AI_IMAGE_SIZE=1024x1024 |
| 146 | #AI_PROVIDER_TIMEOUT_MS=30000 |
| 147 | # USD per million tokens, longest model-prefix match: {"gpt-5-nano":{"in":0.05,"out":0.4,"cached":0.005}} |
| 148 | # Each entry may also carry its platform.rate-card@1 terms: source (URI), verified_at, effective_from (YYYY-MM-DD), |
| 149 | # free_allowance (tokens per metric) and reset_period (day|month|none). Unset = unknown (1970-01-01, urn:openvibe:ai:unverified:*). |
| 150 | #AI_PRICING_JSON= |
| 151 | #AI_INPUT_COST_PER_MTOK=3 |
| 152 | #AI_OUTPUT_COST_PER_MTOK=15 |
| 153 | |
| 154 | # ── DeepSeek as its own provider (chat models; the same key research uses) ── |
| 155 | # With DEEPSEEK_API_KEY (or AI_DEEPSEEK_KEY_REF) set, boot registers provider `deepseek` and its two chat |
| 156 | # models (deepseek-flash, deepseek-v4-pro) with prices from DeepSeek's own price page. Without the key it |
| 157 | # stays disabled and its models are not offers. |
| 158 | #AI_DEEPSEEK_ENABLED=true |
| 159 | #AI_DEEPSEEK_BASE_URL=https://api.deepseek.com |
| 160 | #AI_DEEPSEEK_KEY_REF=env:DEEPSEEK_API_KEY |
| 161 | #AI_DEEPSEEK_TIMEOUT_MS=90000 |
| 162 | |
| 163 | # ── Spend safety for runs nobody pays for ── |
| 164 | # The free allowance (a service naming a person, without their own token) and the anonymous Try it may use only |
| 165 | # offers from these sources AND at or below this output price in USD per 1M tokens. A source is a provider key; |
| 166 | # `local` covers every local model server (local, local2, whisper.cpp) and `community` the community machines. |
| 167 | # Anything else needs the person's own Balance (their own token) or their own key; the router answers 402 |
| 168 | # billing.payer_required instead. Default providers: deepseek,local,community. Default ceiling: 1.5. |
| 169 | #AI_FREE_PROVIDERS=deepseek,local,community |
| 170 | #AI_FREE_MAX_OUTPUT_PRICE_USD_PER_MTOK=1.5 |
| 171 | |
| 172 | # ── Abuse prevention for the free tier (plan E5; server/abuse/, docs/abuse-prevention.md) ── |
| 173 | # The full daily allowance (verified tier); a new account gets a quarter, over 7 days half, kudos add up to |
| 174 | # one more full. Zero turns the free allowance off. The salt keys the device cookie and every hash; set it. |
| 175 | #AI_FREE_DAILY_TOKENS=20000 |
| 176 | #AI_ABUSE=1 |
| 177 | #AI_ABUSE_SALT= |
| 178 | # Network trust signals (account age, passkey, links, 30-day activity) through the identity route. |
| 179 | #AI_ABUSE_NETWORK=1 |
| 180 | # Only these service-token ids may forward x-ov-client-ip / x-ov-device-id (the Exchange). |
| 181 | #AI_FIRST_PARTY_SERVICES=exchange |
| 182 | # Set for openvibe.exchange so the site and api.openvibe.exchange share the signed device cookie. |
| 183 | #AI_DEVICE_COOKIE_DOMAIN=.openvibe.exchange |
| 184 | # The local monthly iptoasn.com table (node scripts/update-ip2asn.js); never a paid API. |
| 185 | #AI_IP2ASN_V4=/srv/openvibe/ai/data/ip2asn/ip2asn-v4.tsv |
| 186 | #AI_IP2ASN_V6=/srv/openvibe/ai/data/ip2asn/ip2asn-v6.tsv |
| 187 | #ASNs an operator knows are hosting ones, on top of the organisation-name match. |
| 188 | #AI_HOSTING_ASNS= |
| 189 | #AI_ABUSE_RATE_PER_MIN=60 |
| 190 | #AI_ABUSE_HOSTING_RATE_PER_MIN=20 |
| 191 | # Free tokens per minute against the week's per-minute baseline, per ASN, device and input bucket. |
| 192 | #AI_ABUSE_VELOCITY_FACTOR=10 |
| 193 | #AI_ABUSE_VELOCITY_MIN_TOKENS_PER_MIN=10000 |
| 194 | # In-house hashcash for the anonymous Try it (no account); required in production, off in tests. |
| 195 | #AI_POW_REQUIRED=1 |
| 196 | #AI_POW_BITS=18 |
| 197 | #AI_POW_TTL_MS=300000 |
| 198 | #AI_POW_CHALLENGES_PER_MIN=10 |
| 199 | |
| 200 | # Optional local HTTP seam provider (POST {operation, request} -> {text, json, usage}) |
| 201 | #AI_HTTP_SEAM_URL=http://127.0.0.1:4799/complete |
| 202 | # A local model server speaking the OpenAI API (llama.cpp llama-server, Ollama /v1): provider `local`, route |
| 203 | # media.local, used by media.analyze's overview. No key; never a paid provider. |
| 204 | #AI_LOCAL_LLM_URL=http://127.0.0.1:8090/v1 |
| 205 | # media.analyze reads a recording where it lies through a loopback reader (server/media-proxy.js): nothing is |
| 206 | # downloaded, and speech windows seek with range requests. 0 downloads it first (the disk cap then applies). |
| 207 | #AI_MEDIA_STREAM=1 |
| 208 | # Object storage Media redirects offloaded recordings to (presigned URLs), followed only from Media itself. |
| 209 | #AI_MEDIA_STORAGE_HOSTS=*.backblazeb2.com,*.r2.cloudflarestorage.com,edge.openvibe.media |
| 210 | #AI_LOCAL_LLM_MODEL=qwen2.5-1.5b-instruct |
| 211 | #AI_LOCAL_LLM_TIMEOUT_MS=180000 |
| 212 | # The host's model is a last offer for short text only: never more than this many tokens, and one generation at |
| 213 | # a time across local and local2, so an outage upstream cannot starve the host. |
| 214 | #AI_LOCAL_LLM_MAX_TOKENS=512 |
| 215 | #AI_LOCAL_LLM_CONCURRENCY=1 |
| 216 | # A second local chat model server (its own llama.cpp process and another model family): provider `local2`, |
| 217 | # so a caller can pin two local offers, e.g. Games' AI-vs-AI matches. Same rules: no key, free, no JSON. |
| 218 | #AI_LOCAL_LLM2_URL=http://127.0.0.1:8092/v1 |
| 219 | #AI_LOCAL_LLM2_MODEL=llama-3.2-1b-instruct |
| 220 | #AI_LOCAL_LLM2_TIMEOUT_MS=180000 |
| 221 | # A second local OpenAI-compatible server for embeddings, when the chat model's llama.cpp process cannot also |
| 222 | # serve the embedding GGUF (llama.cpp serves one model per process). Default: AI_LOCAL_LLM_URL. |
| 223 | #AI_LOCAL_EMBED_URL=http://127.0.0.1:8091/v1 |
| 224 | # An embedding model the local server answers at /embeddings: `local` then serves default.embedding as its free |
| 225 | # member. The server must really serve embeddings (llama-server only with --embedding and an embedding GGUF; a |
| 226 | # chat-only model answers 400 and every embedding call falls over to the shared provider). Vector dimensions |
| 227 | # differ per upstream: never compare or mix vectors from different providers. Unset: no local embeddings. |
| 228 | #AI_LOCAL_EMBED_MODEL=bge-small-en-v1.5 |
| 229 | # media.analyze: file cap (also bounded by free temp disk minus the reserve), download timeout, scene threshold |
| 230 | #AI_MEDIA_ANALYSIS_DIR=/var/lib/openvibe-ai/media-tmp (default: data/media-tmp; never a tmpfs) |
| 231 | #AI_MEDIA_ANALYSIS_MAX_BYTES=4294967296 |
| 232 | #AI_MEDIA_ANALYSIS_DISK_RESERVE_BYTES=3221225472 |
| 233 | #AI_MEDIA_ANALYSIS_FETCH_TIMEOUT_MS=1200000 |
| 234 | #AI_MEDIA_SCENE_THRESHOLD=10 |
| 235 | #FFMPEG_BIN=ffmpeg |
| 236 | #FFPROBE_BIN=ffprobe |
| 237 | # Let routes fall back to the deterministic stub when every real provider fails. Default: on outside |
| 238 | # production, off in production (synthetic output must never stand in for a real answer there). |
| 239 | #AI_STUB_FALLBACK=false |
| 240 | |
| 241 | # ── AI Exchange P4 (CHECKPOINT D3): the truthful model catalog, exact ids and dynamic aliases ── |
| 242 | # Off: today's behaviour (one virtual `openvibe` model, no disclosure object). On: |
| 243 | # - GET /v1/models lists the registry's real models in the ai.model@1 shape plus the dynamic aliases |
| 244 | # openvibe/auto, openvibe/economy, openvibe/balanced, openvibe/fast, openvibe/private (legacy openvibe = auto); |
| 245 | # - `model` may be an exact id `<org>/<model>[:<quant>]@<revision>` (it runs only that artifact, or fails with |
| 246 | # 400 ai.model_unavailable / 404 ai.model_not_found, and never substitutes another model), or one of those aliases; |
| 247 | # - every response carries the `openvibe` disclosure (a leading SSE comment and the final chunk when streaming); |
| 248 | # - paid managed routing uses only offers whose provider_access (API /api/v1/providers) is OWN_DEPLOYMENT or |
| 249 | # AUTHORIZED_WHOLESALE. RESTRICTED and UNVERIFIED (the default) offers are not resold; a person's own key is |
| 250 | # CUSTOMER_BYOK and never goes through that gate. |
| 251 | # Exact ids need artifact metadata on the model row (POST/PATCH /api/v1/models `metadata`: org, model, quant, |
| 252 | # revision, family, variant, sha256, license, rate_version). |
| 253 | #AI_MODEL_CATALOG=1 |
| 254 | |
| 255 | # ── Local speech-to-text: whisper.cpp, the same names Live uses ── |
| 256 | #WHISPER_BIN=/home/ubuntu/whisper.cpp/build/bin/whisper-cli |
| 257 | #WHISPER_MODEL=/home/ubuntu/whisper.cpp/models/ggml-base.en.bin |
| 258 | #WHISPER_MODEL_LIVE= |
| 259 | #WHISPER_MODEL_MULTI=/home/ubuntu/whisper.cpp/models/ggml-small.bin |
| 260 | #WHISPER_VAD_MODEL= |
| 261 | #WHISPER_VAD=1 |
| 262 | #WHISPER_THREADS=4 |
| 263 | #WHISPER_BEAM=1 |
| 264 | #WHISPER_MAX_CONCURRENT=1 |
| 265 | # NVIDIA Parakeet TDT 0.6B v3 for English and 24 European languages (whisper keeps the rest and takes any failure). |
| 266 | # Set the model to turn it on; the binary defaults to parakeet-cli next to WHISPER_BIN. ASR_ENGINE=whisper forces whisper. |
| 267 | #PARAKEET_MODEL=/home/ubuntu/whisper.cpp/models/ggml-parakeet-tdt-0.6b-v3-q8_0.bin |
| 268 | #PARAKEET_BIN= |
| 269 | #ASR_ENGINE= |
| 270 | |
| 271 | # ── Quotas seeded at boot (admin API edits them afterwards; 0 = none) ── |
| 272 | # Live's ai_max_cost_usd_per_day: one daily spend cap across every caller |
| 273 | AI_MAX_COST_USD_PER_DAY=0 |
| 274 | # Requests per minute / per day for EACH calling service |
| 275 | #AI_QUOTA_SERVICE_RPM=600 |
| 276 | #AI_QUOTA_SERVICE_RPD=50000 |
| 277 | |
| 278 | # ── Runs, cache, logs ── |
| 279 | #AI_MAX_WAIT_MS=60000 |
| 280 | #AI_MAX_CONCURRENT_RUNS=4 |
| 281 | # Runs waiting for a slot; past either cap a new run is refused (429 queue.full, Retry-After) |
| 282 | #AI_MAX_QUEUED_RUNS=200 |
| 283 | #AI_MAX_QUEUED_RUNS_PER_CALLER=50 |
| 284 | #AI_CACHE_TTL_SEC=604800 |
| 285 | #AI_MAX_INPUT_BYTES=6291456 |
| 286 | #AI_RUN_RETENTION_DAYS=30 |
| 287 | # Compiled-schema cache (LRU shared by every caller; ai.extract/ai.enrich schemas are caller-supplied) |
| 288 | #AI_SCHEMA_CACHE_MAX=500 |
| 289 | #AI_SCHEMA_CACHE_MAX_BYTES=8388608 |
| 290 | # Raw prompt/response logging: opt-in debugging only, and only for runs whose caller sets options.debug |
| 291 | #AI_DEBUG_RAW_LOG=false |
| 292 | #AI_BREAKER_FAILURES=3 |
| 293 | # The pause after a run of transient failures (timeouts, 5xx, a broken stream): 30 seconds, doubling on every |
| 294 | # failed probe up to 10 minutes. Per provider+model (migrations/0060), so another offer still answers. |
| 295 | #AI_BREAKER_BACKOFF_MS=30000 |
| 296 | #AI_BREAKER_MAX_BACKOFF_MS=600000 |
| 297 | # A provider that answered "insufficient balance" (HTTP 402, or a 429/403 insufficient_quota) or refused its key |
| 298 | # (401/403) opens that offer's circuit at once. This long after it opened (15 minutes) the next real request may |
| 299 | # probe it once; it stays open until a probe succeeds or the provider's key changes. |
| 300 | #AI_BREAKER_EXHAUSTED_COOLDOWN_MS=900000 |
| 301 | # The owner's Network subject (usr_…): one notification per open balance/auth circuit, through the Network |
| 302 | # notification push. Unset means no push; the console, the catalogue and the metrics still say what happened. |
| 303 | #AI_OWNER_SUBJECT= |
| 304 | #AI_PROVIDER_RETRIES=1 |
| 305 | #AI_PROVIDER_RETRY_DELAY_MS=800 |
| 306 | # Hourly balance probe (DeepSeek's GET /user/balance) exposed as ai_provider_balance_usd{provider} on /metrics; |
| 307 | # the operator alert fires below $2. Off under a restore drill (AI_DRILL=1). |
| 308 | #AI_BALANCE_PROBE=1 |
| 309 | #AI_BALANCE_PROBE_INTERVAL_MS=3600000 |
| 310 | #AI_BALANCE_PROBE_TIMEOUT_MS=10000 |
| 311 | |
| 312 | # ── Media inputs: only these https hosts are fetched (plus the Media internal origin) ── |
| 313 | OV_MEDIA_URL=https://openvibe.media |
| 314 | #OV_MEDIA_INTERNAL_URL=http://127.0.0.1:4100 |
| 315 | #AI_FETCH_ALLOW_HOSTS=openvibe.media,openvibe.live,openvibe.network,*.openvibe.network,openvibe.community,openvibe.tools,openvibe.games |
| 316 | #AI_FETCH_MAX_BYTES=536870912 |
| 317 | #AI_FETCH_MAX_IMAGE_BYTES=12582912 |
| 318 | #AI_FETCH_TIMEOUT_MS=120000 |
| 319 | |
| 320 | # MegaSource research (server/research/, research.gather): web search asks the Search Fabric's web scope first |
| 321 | # (OpenVibe.Search POST /api/v1/query) as AI's service principal, and falls back to DeepSeek when the fabric is off, |
| 322 | # refuses or answers nothing. Network must grant `ai` search.query.delegate on audience openvibe.search (Search's |
| 323 | # viewer requires it for a service token); the route's public capability is search.fabric.query. |
| 324 | #OV_SEARCH_INTERNAL_URL=http://127.0.0.1:4710 |
| 325 | #AI_RESEARCH_SEARCH=1 |
| 326 | #AI_RESEARCH_SEARCH_TIMEOUT_MS=15000 |
| 327 | # DeepSeek's paid search is the fallback. The key is read by name; set DEEPSEEK_API_KEY (or point |
| 328 | # AI_RESEARCH_DEEPSEEK_KEY_REF elsewhere). |
| 329 | #AI_RESEARCH_DEEPSEEK=1 |
| 330 | #AI_RESEARCH_DEEPSEEK_KEY_REF=env:DEEPSEEK_API_KEY |
| 331 | #AI_RESEARCH_DEEPSEEK_DAILY=1500 |
| 332 | #AI_RESEARCH_TIMEOUT_MS=15000 |
| 333 | |
| 334 | # ── Exchange webhooks (server/exchange/webhooks.js): a developer registers an https receiver and AI signs |
| 335 | # usage/earnings/offer.down with X-OpenVibe-Signature-V2, the header openvibe-sdk/exchange verifies. A delivery is |
| 336 | # retried with an exponential backoff; the receiver URL goes through the egress guard. On by default. |
| 337 | #AI_WEBHOOKS=1 |
| 338 | #AI_WEBHOOK_TIMEOUT_MS=10000 |
| 339 | #AI_WEBHOOK_MAX_BYTES=65536 |
| 340 | #AI_WEBHOOK_MAX_ATTEMPTS=6 |
| 341 | #AI_WEBHOOK_MAX_PER_OWNER=10 |
| 342 | #AI_WEBHOOK_BASE_DELAY_MS=10000 |
| 343 | #AI_WEBHOOK_MAX_DELAY_MS=3600000 |
| 344 | #AI_WEBHOOK_TICK_MS=1000 |
| 345 | |
| 346 | # ── The 18+ board (server/willing/): two measurement jobs, both OFF by default and both budgeted ── |
| 347 | # The willingness job asks every live text offer the published 60 lawful adult prompts (server/willing/datasets/ |
| 348 | # prompts.json) and records the refusal and hedge rates, each with n and a Wilson interval. The |
| 349 | # abliteration tax runs the objective set (server/willing/datasets/objective-set.json) on each declared |
| 350 | # base/abliterated pair (server/willing/datasets/abliteration-pairs.json) and records the score difference with |
| 351 | # its Newcombe interval. Neither spends anything until enabled AND given a budget above zero; GET |
| 352 | # /api/v1/exchange/willingness?plan=1 and .../abliteration-tax?plan=1 are the dry runs. |
| 353 | # An uncensored offer is asked under the adult tier, which needs a real 18+ measurement account |
| 354 | # (usr_…); without it the plan is blocked instead of bypassing the 18+ gate. The hard floor still holds. |
| 355 | #AI_WILLINGNESS_JOB=1 |
| 356 | #AI_WILLINGNESS_BUDGET_USD=0 |
| 357 | #AI_WILLINGNESS_SUBJECT= |
| 358 | #AI_WILLINGNESS_INTERVAL_MS=604800000 |
| 359 | #AI_WILLINGNESS_MAX_OFFERS=500 |
| 360 | #AI_WILLINGNESS_OUTPUT_TOKENS=256 |
| 361 | #AI_ABLITERATION_TAX=1 |
| 362 | #AI_ABLITERATION_TAX_BUDGET_USD=0 |
| 363 | #AI_ABLITERATION_TAX_SUBJECT= |
| 364 | #AI_ABLITERATION_TAX_INTERVAL_MS=604800000 |
| 365 | #AI_ABLITERATION_TAX_OUTPUT_TOKENS=256 |
| 366 |