openvibers/openvibe.ai .env.example

.env.example

main 21.2 KB · 366 lines · plaintext

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
9PORT=4700
10HOST=127.0.0.1
11NODE_ENV=development
12BASE_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.
23DATABASE_URL=
24DATABASE_DIRECT_URL=
25VALKEY_URL=
26VALKEY_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.
31OV_NETWORK_URL=https://openvibe.network
32OV_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.
137AI_PROVIDER=
138#AI_BASE_URL=https://api.openai.com/v1
139AI_API_KEY=
140# Blank = claude-sonnet-5 for anthropic, gpt-4o-mini otherwise (Live's defaults)
141AI_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
273AI_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) ──
313OV_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