# Cross-Model Verification Protocol (v3.0) ## Overview This protocol enables optional blind cross-model checks for high-stakes AI judgments. When enabled, another model family can inspect bounded inputs without seeing the primary result. That adds typed substrate diversity and may expose shared-frame blind spots; it does not by itself establish independent error processes or higher accuracy. **This is entirely optional.** All ARS skills work with the primary Claude model alone. Cross-model verification is an additional layer for users who want higher confidence in integrity checks, devil's advocate challenges, and review judgments. **Consent boundary:** Before unpublished manuscripts, private notes, corpus text, reviewer comments, decision letters, response letters, or other review material is sent to an external provider, the agent must identify the provider, model, and content class that would be sent, then obtain explicit user consent. An environment variable alone is not consent to upload user content. If consent is not granted, continue with single-model verification. **Citation-only ChatGPT-subscription transport (#630):** A fourth, deliberately narrow transport is available only for the one-reference citation-integrity calls in Stage 2.5 / 4.5. Setting `ARS_CROSS_MODEL_TRANSPORT=codex` selects the contained Codex app-server adapter described below. It does not authorize or implement DA, Reviewer 2, calibration, re-review judgment, design-freeze, editorial-decision, or generic handoff calls; those paths continue to require their documented provider API credentials. The selector is closed: unset or `api` keeps the existing API route, `codex` selects this citation-only route, and every other value fails visibly without falling back. This runtime boundary does not forbid a separately preregistered, offline held-out suite from choosing Codex CLI as its subject transport. In particular, the #684 constructive-value plan is a synthetic evaluation with its own frozen call plan, USD 0 API ceiling, and human expert labels; it is not a generic reviewer/DA handoff and must not call this citation adapter with reviewer data. ## Why Cross-Model Verification A stress test of 68 AI-generated citations found 31% had problems — and all passed three rounds of same-model integrity checks. The root cause: the verifying AI and the generating AI share the same training data distribution, so they share the same blind spots. A different model (trained on overlapping but not identical data, with different RLHF tuning) can catch errors that the primary model systematically misses. **What it improves:** Different models catch different types of hallucination patterns. The post-verification error rate has never been measured — the residual-rate hypothesis (that cross-model checks cut the 31% above to single digits) is unvalidated. **What it doesn't solve:** Frame-lock (all LLMs share most training data), sycophancy (all RLHF models have this tendency). These are degree improvements, not kind improvements. ## Supported Models | Model | API ID | Provider | Best For | |-------|--------|----------|----------| | Claude (session model) | _(inherited Claude Code session model — e.g., Fable 5)_ | Anthropic | Primary model (default for all ARS skills) | | GPT-5.6 Sol | `gpt-5.6-sol` | OpenAI | Cross-verification — current OpenAI flagship, recommended OpenAI verifier; **validated for the ChatGPT-subscription citation transport** (2026-08-19/20 bakeoff, superiority on recall + latency — `audits/bakeoff-gpt-5-6-sol-codex-2026-08-19.md`); **provisional pending ARS validation** on the first-party API route (same standard rates as GPT-5.5) | | Gemini 3.1 Pro | `gemini-3.1-pro-preview` | Google | Cross-verification — current Google flagship (validated); strong at factual verification | | GPT-5.5 | `gpt-5.5` | OpenAI | Cross-verification — previous generation, superseded by GPT-5.6 (2026-07-09); validated, remains fully supported (supports `xhigh` reasoning) | | GPT-5.5 Pro | `gpt-5.5-pro` | OpenAI | Cross-verification — previous generation; validated; strongest GPT-5.5-line reasoning (premium pricing: ~6× GPT-5.5) | ### OpenAI-compatible providers (Chat Completions API — UNGROUNDED, opt-in) | Provider | Example API ID(s) | Endpoint (`ARS_OPENAI_COMPAT_BASE_URL`) | Notes | |----------|-------------------|------------------------------------------|-------| | Xiaomi MiMo | `mimo-v2.5-pro` | `https://token-plan-cn.xiaomimimo.com/v1` | Set `ARS_OPENAI_COMPAT_API_KEY` + `ARS_CROSS_MODEL`. Ungrounded: positive verdicts never count as citation agreement. | | DeepSeek | `deepseek-v4-pro` | `https://api.deepseek.com/v1` | Set `ARS_OPENAI_COMPAT_API_KEY` + `ARS_CROSS_MODEL`. Ungrounded. | | Any OpenAI-compatible | any non-`gpt-*`/`gemini-*` id | any `/v1/chat/completions` endpoint | Routing is governed solely by `ARS_OPENAI_COMPAT_BASE_URL`; the model id must NOT match a first-party prefix or it takes the grounded first-party route instead. | > **Compatible providers are ungrounded.** They expose no hosted web-search tool, so there is no grounding evidence behind a verdict. A positive `VERIFIED` is downgraded to `NOT_SEARCHED` and never counts as agreement in citation verification; a `NOT_FOUND`/`MISMATCH` survives as a disagreement. They ARE first-class for Devil's Advocate critique (which needs no grounding) — but a DA finding from any provider is an adversarial hypothesis, not standalone evidence, unless independently sourced. **Recommended cross-verification pair:** the inherited Claude session model (primary) + a current-generation second-family verifier — Gemini 3.1 Pro (validated) or GPT-5.6 Sol (provisional; see the note below). > The primary row deliberately names no version: the primary is always the session model, so the row cannot go stale on the next Anthropic release. Verifier IDs stay concrete because they are literal API strings the user must export. (`gpt-5.4` / `gpt-5.4-pro` remain accepted for existing setups.) > **GPT-5.6 Sol is provisional (listed 2026-07-11, three days after release).** Its endpoint support (Responses API), hosted `web_search` tool, and reasoning-effort values are confirmed against OpenAI's model documentation, but its ARS-specific behavior — grounded-search completion rate, citation-mismatch recall, false-disagreement rate, response-shape stability against the jq grounding guards, p95 latency — is unvalidated. **Recommendation policy (2026-08-19):** GPT-5.5 was superseded by the GPT-5.6 family on 2026-07-09, so the recommendation names the current generation rather than a superseded id — a lifecycle decision, not a measurement claim. `validated` is earned only there — and on 2026-08-19 a codex-transport bakeoff run earned it for the **ChatGPT-subscription citation transport**, with a measured superiority case from the counterbalanced gate fleet (fabrication recall 0.90 vs 0.80, p95 latency 25.0 s vs 49.6 s nearest-rank, grounded completion tied, no inferiority on any measure; recall and latency led in all five paired fleets — `audits/bakeoff-gpt-5-6-sol-codex-2026-08-19.md`). On the **first-party API route** `gpt-5.6-sol` stays **provisional** — that run did not exercise the API route's jq grounding guards, and no parity or superiority is claimed there. For the API route, run `scripts/cross_model_smoke_test.sh` against your key before adopting it; users who prefer an API-route-validated id can stay on `gpt-5.5` or `gemini-3.1-pro-preview` (validated = the id-status allowlist below; the API route has no recorded bakeoff run). Two facts that differ from the GPT-5.5 lineup: GPT-5.6 ships **no `-pro` model ID** — premium operation is standard `gpt-5.6-sol` plus `reasoning: {mode: "pro"}` in the request, billed at standard token rates with more model work per request (the old fixed ~6× unit-price split does not carry over); and its reasoning effort accepts `none|low|medium|high|xhigh|max` (GPT-5.5 tops out at `xhigh`), defaulting to `medium` in both standard and pro modes. Using two non-Anthropic models as primary+verifier is possible but not tested with ARS prompts. ## Setup Guide ### Prerequisites You need API keys from at least one additional provider. ARS itself runs inside Claude Code, so Claude is always available as the primary model. ### Step 1: Get API Keys **OpenAI (GPT-5.6 Sol / GPT-5.5):** 1. Go to [platform.openai.com/api-keys](https://platform.openai.com/api-keys) 2. Create a new API key 3. Copy the key (starts with `sk-`) **Google (Gemini 3.1 Pro):** 1. Go to [aistudio.google.com/apikey](https://aistudio.google.com/apikey) 2. Create a new API key 3. Copy the key (starts with `AIza`) **OpenAI-compatible providers (MiMo / DeepSeek / self-hosted):** 1. Get an API key from your provider (e.g. [platform.deepseek.com](https://platform.deepseek.com) or the Xiaomi MiMo platform) 2. Note the provider's API root including `/v1` (e.g. `https://api.deepseek.com/v1`) 3. The key goes in `ARS_OPENAI_COMPAT_API_KEY` and the endpoint in `ARS_OPENAI_COMPAT_BASE_URL` — NOT in `OPENAI_API_KEY`/`OPENAI_BASE_URL` (your real OpenAI key is never sent to a third-party endpoint) 4. The compatible model id (`ARS_CROSS_MODEL`) must NOT begin with a `gpt-` or `gemini-` prefix. Any such id is claimed by the first-party grounded route, so a self-hosted compatible model named that way would be routed to the (unavailable) first-party path instead of your compatible endpoint. ### Step 2: Set Environment Variables Add to your shell profile (`~/.zshrc` or `~/.bashrc`): ```bash # Cross-model verification for ARS — pick exactly ONE provider tuple. # --- Option A: OpenAI (first-party, grounded) --- export OPENAI_API_KEY="" # Current OpenAI flagship — provisional pending ARS validation (see Supported Models; # run scripts/cross_model_smoke_test.sh against your key before relying on it): export ARS_CROSS_MODEL="gpt-5.6-sol" # Previous generation, validated (designated bakeoff baseline): # export ARS_CROSS_MODEL="gpt-5.5" # Optional: reasoning effort for OpenAI verifier calls (unset = the provider's own # default for the chosen model). GPT-5.6 accepts none|low|medium|high|xhigh|max; # GPT-5.5 tops out at xhigh. # export ARS_CROSS_MODEL_REASONING_EFFORT="medium" # --- Option B: Google Gemini (first-party, grounded) --- export GOOGLE_AI_API_KEY="" export ARS_CROSS_MODEL="gemini-3.1-pro-preview" # --- Option C: OpenAI-compatible provider (MiMo / DeepSeek / self-hosted) — UNGROUNDED --- # Uses a DEDICATED key; your real OPENAI_API_KEY is never sent to a third-party endpoint. export ARS_OPENAI_COMPAT_BASE_URL="https://api.deepseek.com/v1" # API root incl. /v1 export ARS_OPENAI_COMPAT_API_KEY="" export ARS_CROSS_MODEL="deepseek-v4-pro" # provider id, NOT gpt-*/gemini-* ``` Then reload: `source ~/.zshrc` ### ChatGPT subscription option — citation integrity only Users already authenticated to Codex with a ChatGPT subscription may select the contained citation adapter without supplying `OPENAI_API_KEY`. This is not a general API-key replacement and does not activate any non-citation cross-model surface. Codex CLI 0.147.0 or newer is required; `codex login status` must return exactly `Logged in using ChatGPT`. A custom `CODEX_HOME` is honored consistently by detection and execution. ```bash # Citation-integrity calls only. General DA/reviewer/judgment calls remain on API transport. export ARS_CROSS_MODEL_TRANSPORT="codex" # gpt-5.6-sol is validated for THIS transport (2026-08-19 codex-transport bakeoff, # superiority on recall + latency — audits/bakeoff-gpt-5-6-sol-codex-2026-08-19.md). # gpt-5.5 remains the validated bakeoff baseline alternative. export ARS_CROSS_MODEL="gpt-5.6-sol" python3 scripts/cross_model_codex_transport.py detect # The producer sends one closed codex_citation_request/1.0 object on stdin: printf '%s' "$CITATION_REQUEST_JSON" | scripts/cross_model_codex_verify.sh ``` The adapter launches an ephemeral app-server thread from an empty working root, copies only `auth.json` into a private temporary `CODEX_HOME`, strips credential environment variables, sets read-only sandboxing with approvals disabled, exposes no dynamic tools or workspace roots, and disables local execution/file/app/plugin/ skill/browser/agent capabilities. It accepts only the closed request schema and emits only the closed receipt schema under `shared/contracts/cross_model/`; it never accepts a path or caller-authored prompt. Authentication detection reports the mode only—credentials never enter the receipt or diagnostic output. Grounding authority is the app-server `webSearch.results[]` event, not the model's text and not a URL echoed from the request. Every positive source receipt binds an exact canonical HTTPS URL to the search item id, result index, and canonical result digest. A reference-bound search with no matching work may yield `NOT_FOUND`; a missing/malformed search, unrelated query, unbound source, multiple final answer, forbidden tool event, or wrong event shape fails closed to `NOT_SEARCHED`. The ordinary `codex exec --json` projection is intentionally not used because it does not preserve the structured standalone-search results needed for that binding. `scripts/cross_model_smoke_test_codex.sh` is a manual live smoke using public Vaswani et al. citation metadata. It consumes subscription/model/network capacity, so CI never invokes it; run it only after consenting to that live call. CI uses a fake Codex app-server and checked-in event fixtures exclusively. ### Step 3: Verify Setup In Claude Code, you can test by asking: ``` Check if cross-model verification is available for ARS ``` The system will check for the environment variables and report which models are available. ### Step 4: Enable Per-Session (Optional) If you don't want cross-model verification running all the time, you can enable it per session: ```bash # Enable for this session only export ARS_CROSS_MODEL="gpt-5.6-sol" # Disable for this session unset ARS_CROSS_MODEL ``` ## How It Works in Each Skill ### Integrity Verification (academic-pipeline, Stage 2.5 / 4.5) **When `ARS_CROSS_MODEL` is set:** - Primary model (Claude) runs full Phase A-E verification as normal - After Phase A completes, a **risk-stratified** selection of references is sent for a blind cross-model verification pass (see step 2 below; replaces the pre-#518 uniform random 30%) - Cross-model receives only the reference text and paper context — not Claude's verification result (to prevent anchoring) - Disagreements are flagged as `[CROSS-MODEL-DISAGREEMENT]` and prioritized for human review **When `ARS_CROSS_MODEL` is not set:** - Standard single-model verification (unchanged from v2.7+) **Implementation for agents:** When the integrity_verification_agent detects `ARS_CROSS_MODEL` in the environment, it should: 1. Complete Phase A verification normally 2. Select references by **risk stratification** (#518; replaces uniform random 30%). Classify each reference at selection time and record the tier in the results table. Four tiers; a reference qualifying for more than one is classified once at the highest tier that applies (precedence: `HIGH-IMPACT` > `NEW-CHANGED` > `CONTROL`/`RANDOM`) and verified once: - **HIGH-IMPACT — verify 100%, no cap (both gates).** A reference is high-impact if it supports any of: (a) a headline conclusion (abstract- or conclusions-level claim); (b) a numerical claim (statistic, effect size, percentage, threshold); (c) a causal claim; (d) a methods-critical claim (the validity of the chosen method rests on it); (e) a disputed claim (already carrying a contradiction disclosure or reviewer split). - **RANDOM (Stage 2.5 only) — the non-high-impact remainder**, sampled at 10%, rounded up (minimum 3, maximum 10; if the remainder has fewer than 3 references, sample all of it). - **NEW-CHANGED (Stage 4.5 only) — verify 100%, no cap:** every reference supporting a claim that is **new or changed** since Stage 2.5, whatever its impact class. - **CONTROL (Stage 4.5 only) — the unchanged, non-high-impact remainder**, sampled at 10%, rounded up (minimum 3, maximum 10; fewer than 3 → all of it) to catch silent drift. At Stage 4.5, CONTROL replaces RANDOM — there is no separate RANDOM tier at the final gate. - Cost scales with the count of high-impact (and, at Stage 4.5, new/changed) citations instead of total reference count — a results-dense paper approaches 100% coverage, which is the point: verification budget concentrates where the paper's weight rests. The old flat cap (max 15) is retired; only the sampled tiers (RANDOM/CONTROL) carry a cap (max 10 each). 3. Issue **one API call per reference** — not a batch. (Batching hides which reference the model actually grounded: a single grounding-metadata trace on a 5-reference response proves *something* was searched, not that *each* reference was. One reference per call makes the grounding evidence 1:1 with the verdict.) For each reference, construct a verification prompt: ``` Verify this academic reference. Check: Does it exist? Are the author names, year, title, journal, and DOI correct? Search the web to confirm — do not answer from memory. Respond with exactly one verdict: - VERIFIED — found online; include at least one source URL or DOI you found - MISMATCH — found, but a field is wrong (state which); include the source - NOT_FOUND — searched, no matching record exists - NOT_SEARCHED — you could not actually search the web for this reference Reference: [full reference text] — Context: [sentence where cited] ``` A `VERIFIED` verdict with no accompanying source URL/DOI is treated as `NOT_SEARCHED` (the model claimed a result it cannot evidence). 4. Send to the cross-model via the appropriate API (see API Call Patterns below). **For first-party providers the call patterns enable the hosted web-search/grounding tool and reject the response as `NOT_SEARCHED` when the API returns no grounding evidence** — a model that ignores the "search the web" instruction cannot fake an absent grounding trace, so this is the real safety boundary, not the prompt wording. **An OpenAI-compatible provider has no grounding tool, so its positive verdicts are downgraded to `NOT_SEARCHED` by the handler (rejections pass through); a compatible provider therefore never contributes a grounded agreement.** 5. Compare results: if Claude said VERIFIED but cross-model said NOT_FOUND or MISMATCH, flag as `[CROSS-MODEL-DISAGREEMENT]`. Treat `NOT_SEARCHED` / ungrounded exactly as **not verified** — it never counts as agreement with a Claude `VERIFIED`, and a sample that returns `NOT_SEARCHED` is surfaced for re-run or human review, never silently passed. 6. Include disagreements in the integrity report under a new section: ```markdown ### Cross-Model Verification Results - References selected: X/Y (Z%) — HIGH-IMPACT: H (100% of tier), RANDOM: R (Stage 2.5), NEW-CHANGED: N + CONTROL: C (Stage 4.5) - Agreements: N - Disagreements: M (listed below, prioritized for human review) - Ungrounded (NOT_SEARCHED): U (the cross-model could not actually search — these are NOT confirmations; re-run or human-review) | # | Reference | Tier | Claude | Cross-Model | Source (URL/DOI) | Status | |---|-----------|------|--------|-------------|------------------|--------| ``` The `Tier` column is `HIGH-IMPACT` / `RANDOM` / `NEW-CHANGED` / `CONTROL` per step 2 (one tier per reference, highest-precedence tier wins). The `Source` column carries the URL/DOI the cross-model returned for a `VERIFIED` row; a blank source on a `VERIFIED` verdict downgrades it to `NOT_SEARCHED`. ### Devil's Advocate (deep-research + academic-paper-reviewer) **When `ARS_CROSS_MODEL` is set:** - After the DA completes its standard review/checkpoint, the cross-model receives the same material without the DA findings and generates a blind critique - The DA then compares: any CRITICAL or MAJOR issues found by the cross-model but not by the DA are added as `[CROSS-MODEL-FINDING]` - This directly addresses frame-lock — a different model may attack from a different angle > A compatible (ungrounded) provider is first-class for DA critique — surfacing weaknesses and attack angles needs no web grounding. But "first-class" is scoped to critique, not factual adjudication: a DA finding from any provider is an adversarial hypothesis, never standalone evidence, unless it carries an independently-checkable source. Do not treat a compatible-provider DA "finding" as a verified defect. **When `ARS_CROSS_MODEL` is not set:** - Standard single-model DA (unchanged) **Implementation:** The DA agent, after completing its checkpoint report, should: 1. Send the reviewed material + a simplified DA prompt to the cross-model: ``` You are a devil's advocate reviewing this [research/paper]. Find the most serious weaknesses — every one the evidence supports, ranked most severe first; no fixed count, and do not pad to reach one (#574 A1). For each, state: - What the weakness is - Why it matters - What the strongest counter-argument would be Material: [the reviewed content] ``` 2. Compare cross-model findings with own findings 3. Any cross-model finding not already covered → add to report as `[CROSS-MODEL-FINDING]` 4. Log: `[CROSS-MODEL: X findings received, Y novel (not in primary DA report)]` ### Cross-Model Reviewer Track (#540 — academic-paper-reviewer full mode) **Activation — consent, not configuration:** the track activates only inside the same consent boundary as every cross-model feature in this document (the manuscript is uploaded to the external provider): `ARS_CROSS_MODEL` being set is configuration, and the user's explicit cross-model consent for the session is the authorization. Configured-but-unconsented runs behave exactly like the not-set case below. **When active:** - ONE existing peer-reviewer slot (Reviewer 2 by default) runs on the cross-model family instead of the session model. The panel stays FIVE seats — this is a substrate swap inside a fixed slot, NOT the retired "6th reviewer" (see the retirement note above: its five counterproductive conditions — score averaging, role duplication, findings-as-confirmed-defects, majority-vote false confidence, synthesizer context burn — all attach to an ADDED generic seat; none applies to swapping the substrate of an existing persona with an unchanged role and an unchanged vote). - Transport follows #523 ownership: the dispatching layer (the main session running the reviewer skill — not a Bucket A agent) executes the API calls, mirroring the in-session phase inputs exactly: call 1 = the Phase 1 system persona + the contract JSON + the paper METADATA that in-session Phase 1 receives (paper content withheld, per the sprint protocol's Phase 1 input spec); call 2 = the re-injected contract + the Phase 2 system prompt + call 1's output wrapped in the `` data delimiter + the paper wrapped in the `` data delimiter (#574 A6, in lockstep with `sprint_contract_protocol.md` §2 step 4 — the cross-model seat receives the manuscript inside the same fence as in-session seats). The delimiters are the conversation linkage — no server-side session state is assumed. - The dispatching layer hands the synthesizer the slot's report PLUS the actual seat-level provenance observation (role ID, invocation-context ID, peer-output visibility, actor type, model family, provider, and any accountable human-reviewer ID). It builds the `reviewer_full`-bound `review-panel-provenance/1.0` artifact over the exact EIC/R1/R2/R3/DA roster, then raw-byte and replay-validates its closed Schema 6 carrier with `scripts/review_panel_provenance.py`; the synthesizer fills the Review Panel Provenance block from that artifact, never from a persona, intended route, or configured provider. - The slot's report enters the panel matrix exactly as that slot's report always does — heterogeneity itself is the §5.2 safeguard. The synthesizer computes NO cross-family aggregate and NO "same-model majority" (any such aggregation is on its forbidden-operations list): cross-family splits are visible by inspection in the panel matrix the user already receives, and the provenance block names which seat ran on which family. - An ungrounded compatible provider is first-class here (same class as DA critique: persona judgment needs no web grounding); its factual claims about literature remain subject to the normal citation gates. - Degradation: a failed/unavailable cross-model dispatch falls back to the normal primary-family routing for that seat (the session model, as adjusted by any active `ARS_MODEL_TIERING` policy — tiering is orthogonal and never overridden by this track), and the actual fallback execution is recorded in that seat's typed provenance — never a silent swap-back. If the actual family or provider cannot be established, the observation is omitted/null and the corresponding axis becomes `unknown`; the intended route MUST NOT fill the gap. **When not active** (env unset, or consent not given): - All five personas run on the normal primary-family routing (session model + any active `ARS_MODEL_TIERING` policy), and the Editorial Decision Letter carries the correlated-error disclosure derived from the typed provenance artifact (see the template's Review Panel Provenance block) instead of silently implying independence. **Typed provenance is not a binary independence score (#740).** The closed contract and field semantics are defined in `academic-paper-reviewer/references/review_panel_provenance_protocol.md`. `role_separated`, `fresh_context`, `blind_to_peer_outputs`, `model_family_distinct`, `provider_distinct`, and `human_distinct` remain separate `true` / `false` / `unknown` axes. A fixed seat or persona label does not fill even the role observation; the dispatcher records the actual role and all other axes from execution. No label establishes a binary `independent` value. Same-family execution requires the fixed correlated-error disclosure, while missing family evidence stays `unknown` and carries the unknown-family disclosure. `fresh_context` is fixed to `fresh_context_scope: within_panel_attempt_only`: it compares the five contexts within one artifact and does not prove that a retry or later round used contexts new to attempt history. External motivation: Ren et al. (2026, arXiv:2607.13104 §5.2) — consistency-derived feedback is fragile when errors correlate across samples of one model, and repeated sampling may amplify a confidently-wrong conclusion; heterogeneous critique models are among the safeguards it names. #### Calibration transport exception (#611 — non-sprint, attempt-atomic) This branch applies only to the opt-in `reviewer_calibration` mode. It does not opt calibration into the sprint contract or change the ordinary `reviewer_full` transport above. For each calibration panel, the Reviewer 2 substrate swap is exactly one stateless provider call that byte-for-byte mirrors the same replicate's primary-family calibration Reviewer 2 invocation: the same `domain_reviewer_agent` system persona, that paper's already-frozen Reviewer Configuration Card #3, and the complete manuscript inside the same `...` data fence. The call MUST NOT send a sprint contract, a paper-blind Phase 1 request, ``, any gold label, human score, per-dimension gold, or gold rationale. Its return is the complete standard-mode Reviewer 2 report plus a substrate-provenance stamp for the existing calibration synthesizer. This is a transport-only substitution; it does not change any reviewer prompt, rubric, panel cardinality, or synthesis semantics. **Calibration data-fence collision preflight (closed).** The single-call payload carries Reviewer Configuration Card #3 byte-for-byte inside `...` and the manuscript byte-for-byte inside `...`. Before a payload is sent, test each raw source independently against its own wrapper with the case-insensitive predicates `]*>` and `]*>`, respectively. If either matches, refuse the entire calibration attempt before transport and send no provider call containing either payload. MUST NOT escape, strip, rewrite, truncate, switch delimiters, or fall back to primary routing: those paths break byte parity or send the same colliding content. Exact, whitespace, case, self-closing, and attributed/tolerant-parser closing forms match; a different longer tag such as `` does not match. This preflight is closed at exactly these two tag names; fragments without `>`, entity encodings, and Unicode confusables are outside its delimiter grammar. Expanding this boundary requires the normative paragraph, lint witnesses, and mutation tests to change together. Calibration is repeated-panel measurement, so its fallback is **attempt-atomic** rather than per-seat: 1. Before any scored panel completes, lock one `attempt_id` and one `substrate_plan` (`cross_model_r2` or `primary_only`) without consulting any gold material. Configuration, consent, and a non-content transport preflight happen before this lock. If any is unavailable, warn, lock `primary_only`, disclose the reason, and begin the complete schedule on that plan. 2. Under `cross_model_r2`, every paper and replicate uses the single-call branch above. If a later Reviewer 2 dispatch fails after the attempt begins, mark the entire attempt invalid; every completed panel in that attempt becomes diagnostic-only and MUST NOT enter any aggregate. Never continue the failed paper or a later replicate on primary routing. 3. The only result-producing recovery is a new `attempt_id`, an empty aggregate, and a restart at paper 1 / replicate 1 on one homogeneous plan. Restarting all-primary may spend the whole schedule again, so stop for explicit user authorization unless that retry cost was already authorized. Before a homogeneous attempt finishes, MUST NOT emit full-tier metrics, a directional readout, or either session disclosure. This attempt-atomic override is calibration-only; ordinary `reviewer_full` keeps the per-seat disclosed fallback above. ### Re-Review Judge Provenance (#539/#740 — Stage 3' verification round) **When active** (configured + consented): after the re-review commits its Priority 1 verdicts, the dispatching layer runs a direct blind per-item pass over the § API Call Patterns TRANSPORT (endpoint + auth) with a judgment-specific request — not the citation handlers: no grounding requirement (persona-judgment class), closed verdict set {FULLY_ADDRESSED, PARTIALLY_ADDRESSED, NOT_ADDRESSED, MADE_WORSE}, non-conforming responses → `unavailable`, never coerced; item + author claim + revised passage sent minimized and as data. Results land in the R&R Traceability Matrix's `Cross-model` column (`agree` / `diverges: ` / `unavailable` / `not_configured`) — a `diverges` cell is a review trigger for the Phase 2 synthesis decision, never a vote; `unavailable` is ROW-level (that row carries the same-family caveat). **Run-level disclosure** (the verbatim same-family line in the Re-Review Output, never omitted) applies only when the pass is `not_configured` or EVERY item came back unavailable; mixed runs record `partial — N/M items judged`. Both cases record the Judge Record (actual verification-judge identity; exact replay-validated Round-1 provenance artifact reference/digest and axes, or explicit unknown; prompt/rubric surfaces; evidence seen; judging budget separate from generation) — Schema 6 optional `judge_record`. Cross-family routing and input blinding are typed provenance facts, never a binary independence claim. Authority: `academic-paper-reviewer/references/re_review_mode_protocol.md` § Judge Provenance and Correlated-Error Boundary. External motivation: Ren et al. §8.1.2 — a distinct judge configuration for final reporting plus transparency about the judge's identity, prompt, rubric, and budget; the reviewer's calibration mode approximates the same section's calibration-against-a-verifiable-subset safeguard to the extent the user's gold labels reflect real outcomes. ### Blind Disagreement Checkpoints (research-design freeze + final editorial decision) Two irreversible checkpoints gain an optional cross-model check when `ARS_CROSS_MODEL` is set and the consent gate has been passed: | Checkpoint | Primary owner | Cross-model input (never the primary's decision) | Structured decision enum | |---|---|---|---| | Research-design freeze | `research_architect_agent` (deep-research) | RQ Brief + draft Methodology Blueprint | `sound` / `revise_before_freeze` / `fundamental_concern` | | Final editorial decision | `editorial_synthesizer_agent` (academic-paper-reviewer) | The panel's usable reviewer cards (all `panel_size` N of them — 5 in the default full-mode panel, 2 under `methodology_focus`) + paper metadata | `accept` / `minor_revision` / `major_revision` / `reject` | **Mechanics:** 1. The primary reaches its decision as normal and records it in the SAME structured form as step 3 (the enum + up to 3 drivers + confidence — all three fields) **before** the cross-model is called — both sides commit blind, so the comparison in step 4 is enum-against-enum, not enum-against-prose. Under a sprint contract, the editorial checkpoint runs **after** the mechanical three-step protocol has emitted `editorial_decision` (a post-Step-3 comparison; the contract arithmetic itself is never extended or re-run). 2. The cross-model receives the same input material and a structured-decision prompt. It **never** sees the primary's decision, scores, or reasoning first — the same anchoring-prevention rule as the integrity samples. 3. Output contract: `{decision: , drivers: [up to 3 one-sentence reasons], confidence: low|medium|high}`. 4. Mechanical comparison: **material divergence = differing enum values.** Adjacent categories (e.g. minor vs major revision) are still material; the report notes adjacency. 5. On divergence: a **targeted rebuttal** — the primary must address each cross-model driver specifically against the evidence already on file (reviewer cards / blueprint content), no generic reassurance. Both decisions and the rebuttal surface to the user. The primary's decision stands unless the **user** changes it: disagreement is a review trigger, never a vote, and the two decisions are never averaged. 6. On agreement: one log line `[CROSS-MODEL-CHECKPOINT: agreement — ]`; both structured decisions are still recorded. 7. Graceful degradation: transport failure → `[CROSS-MODEL-ERROR]`, proceed single-model, note in the report (see § Graceful Degradation). **Transport ownership (#523).** Both checkpoint owners are fenced single-phase (Bucket A) agents: the runtime write-scope guard (`scripts/ars_write_scope_guard.py`) denies them ALL Bash, and `research_architect_agent` additionally carries the #514 frontmatter `tools:` allowlist (`Read, Write, Edit, Grep, Glob` — no shell) at dispatch time. A checkpoint owner therefore never executes the § API Call Patterns transport itself when it runs as a dispatched subagent. The contract: the owner commits its structured decision (step 1) and emits the sanitized cross-model input as a **handoff artifact**; the **dispatching layer** — the context that invoked the agent and holds shell capability (the main session running the skill, or `pipeline_orchestrator_agent` in pipeline Mode A; neither is Bucket A) — executes the transport, parses the structured output, and applies the mechanical enum comparison (step 4). Agreement or transport failure → the dispatching layer records the outcome (the audit-surface fill is a mechanical template population from the two committed decisions); divergence → it re-invokes the owner with the cross-model's `{decision, drivers, confidence}` to produce the targeted rebuttal (step 5) — the comparison is mechanical, the rebuttal is the owner's judgment against the evidence on file and is never written by the dispatcher. When the owning role executes inline in a context that itself holds shell capability, owner and dispatching layer are the same context and the handoff is a no-op. **This rule generalizes:** any cross-model call whose primary owner is a Bucket A agent routes its transport through the dispatching layer the same way (e.g. `devils_advocate_reviewer_agent`'s blind, separately executed cross-model DA critique) — with one outcome-routing difference: a call with no mechanical enum comparison (the DA critique) has nothing the dispatcher can resolve itself, so every successful response is returned to the owner for the follow-on judgment, not only divergences. Non-fenced owners with shell capability (`integrity_verification_agent` at the Stage 2.5/4.5 gates, `devils_advocate_agent` in deep-research, the main session) execute § API Call Patterns directly, unchanged. ### Cross-model handoff envelope (#527) The #523 "clearly-delimited cross-model handoff block" has ONE canonical form. `scripts/cross_model_handoff.py` is the **normative grammar** — this prose describes it; the module decides it; the fixtures in `scripts/test_cross_model_handoff.py` pin the owner → dispatcher → owner path with a fake transport. **Envelope (emitted by a dispatched owner, verbatim fences at line start):** ``` [CROSS-MODEL-HANDOFF v1] checkpoint_kind: design_freeze | editorial_decision | da_critique owner_agent: correlation_id: expected_result: enum_comparison | full_return owner_decision: , "drivers": [...], "confidence": ...} — REQUIRED iff enum_comparison; travels OUTSIDE the payload and is NEVER forwarded to the cross-model> payload: [/CROSS-MODEL-HANDOFF] ``` Kind ↔ owner ↔ result-shape triples are closed (normative mapping: `CHECKPOINT_KINDS` + `EXPECTED_OWNERS` in the reference module): `design_freeze` (`research_architect_agent`) is `enum_comparison`; `editorial_decision` (`editorial_synthesizer_agent`) is `enum_comparison` (decision enums per the checkpoint table above); `da_critique` (`devils_advocate_reviewer_agent`) is `full_return`. Any other combination — including an unknown version fence, which is malformed rather than an ordinary deliverable — fails closed. Structured decisions carry ALL THREE fields (`decision`, `drivers`, `confidence`) on both sides; a bare decision never routes to a judgment. **Dispatcher consumer contract** (the main session running the skill, or `pipeline_orchestrator_agent` in pipeline Mode A): 1. **Recognition.** A `[CROSS-MODEL-HANDOFF v1]` fence in a dispatched agent's output is a transport request, never an ordinary deliverable — the dispatcher must not file it as content, summarize it, or drop it. 2. **Validation.** Unknown version fence, missing/duplicate header, unknown `checkpoint_kind`, kind/`expected_result` mismatch, unparseable `owner_decision`, or missing payload → `[CROSS-MODEL-ERROR: malformed_handoff]`, outcome `unavailable`, proceed single-model. Fail-closed: the dispatcher never repairs or guesses. 3. **Transport.** Execute the provider transport per § API Call Patterns (endpoint, auth, model id, timeout/error handling) with the **payload only** as input material — `owner_decision` and everything outside the fences never reach the cross-model (blindness). The REQUEST PROMPT is the owning checkpoint's structured-decision prompt (§ Blind Disagreement Checkpoints, Mechanics steps 2-3) for `enum_comparison`, or the blind-separately-executed-DA-critique prompt for `full_return` — NEVER the citation-verification prompt, its grounding-status guards (`NOT_SEARCHED` / `SOURCES:`), or its citation-status normalization, which would corrupt a judgment response into a citation verdict. This label records blinding and execution separation; it does not assert independent error processes. 4. **Result validation.** For `enum_comparison` the response must parse as `{decision ∈ the kind's enum, drivers ≤ 3, confidence ∈ low|medium|high}`; malformed JSON or an unknown enum value → `[CROSS-MODEL-ERROR: malformed_result]`, outcome `unavailable` — the dispatcher never fabricates or coerces a judgment. 5. **Agreement** (`enum_comparison`, equal enums): the dispatcher performs the mechanical fill (log line + audit-surface population from the two committed decisions) and does **not** re-invoke the owner. 6. **Divergence** (`enum_comparison`, differing enums): the dispatcher re-invokes the ORIGINAL owner with the minimum return context — `correlation_id`, the owner's committed `owner_decision`, the cross-model's full structured result, and the original payload (or a pointer to the same artifact on file) — and the owner writes the targeted rebuttal. The dispatcher never authors it. 7. **Full return** (`full_return`): no comparison exists for the dispatcher to resolve, so EVERY successful response is returned to the owner (`correlation_id` + the response verbatim); the findings comparison is the owner's. 8. **Flag unset.** With `ARS_CROSS_MODEL` unset, owners emit no envelope and behavior is byte-equivalent pre-#527; a stray envelope encountered with the flag unset is logged `[CROSS-MODEL-SKIPPED]` and not transported. Checkpoint decisions are judgment, not lookup — an ungrounded/compatible provider is first-class here, with the same scoping as DA critique: a divergence from any provider is an adversarial hypothesis and a review trigger, never a confirmed defect. > **Why there is no generic "6th reviewer."** An earlier version of this document planned a cross-model 6th reviewer for peer review. That design is retired, not deferred (#518, 2026-07): the conditions under which an extra generic reviewer becomes counterproductive — score averaging, role duplication, findings treated as confirmed defects, majority-vote false confidence, synthesizer context burn — match ARS's documented anti-patterns one-for-one. The blind disagreement checkpoints above are the replacement: cross-model judgment concentrated at the two decisions that are hardest to reverse, compared blind, with divergence escalated to the human instead of blended into a consensus. ## API Call Patterns Four patterns are documented below. The first is the citation-only Codex subscription adapter; the next two (OpenAI and Gemini) are first-party API routes and share the same contract: enable the provider's hosted web-search tool, and **gate the model's text on proof that a search actually happened** — no grounding evidence (an OpenAI `web_search_call` item / a Gemini `groundingMetadata` block) emits `NOT_SEARCHED` and the text is discarded, so this guard, not the prompt wording, is what prevents a from-memory guess being laundered into `VERIFIED`. Both first-party web-search tools are hosted/server-side: one request, no client-side tool-call round-trip. The fourth (OpenAI-compatible) is ungrounded by construction: it has no web-search tool, so the handler downgrades positive verdicts to `NOT_SEARCHED` and lets rejections through, and a compatible verdict never counts as a grounded agreement. `PROMPT` holds the single-reference verification prompt from step 3 for API routes; the Codex adapter instead accepts its closed request object. ### Codex app-server (ChatGPT subscription; citation integrity only) When and only when `ARS_CROSS_MODEL_TRANSPORT=codex`, the Stage 2.5 / 4.5 integrity producer constructs one `ars-codex-citation-request/1.0` JSON object per selected reference (`request_id`, exact `reference_text`, exact `citation_context`) and invokes `scripts/cross_model_codex_verify.sh` on stdin. It validates the one-line response against `codex_citation_receipt.schema.json` before reading `verdict`, `searched`, `reason_code`, or bound `sources[]`. Nonzero exit is a transport error; a valid `NOT_SEARCHED` receipt is an ungrounded result and follows the existing separate handling. No other agent or checkpoint may consume this adapter. The runtime drives app-server v2 rather than `codex exec --json`: only the former retains the structured standalone-search result objects needed for exact source binding. The minimum-privilege, auth-attestation, event-grammar, and containment contract is normative in `docs/design/2026-08-11-630-codex-subscription-citation-transport-spec.md` and machine-checked by the #630 test suite. The Bash entrypoints use syntax compatible with macOS Bash 3.2. ### OpenAI (GPT-5.6 Sol / GPT-5.5 / GPT-5.5 Pro) Use the **Responses API** (`/v1/responses`) — the hosted `web_search` tool lives there. (Chat Completions does not take `tools: [{type: "web_search"}]`; web search on that endpoint requires the separate `gpt-5-search-api` model, so this example targets Responses to stay model-agnostic across `gpt-5.5` / `gpt-5.5-pro` / `gpt-5.6-sol` / the legacy `gpt-5.4*` ids.) ```bash # PROMPT holds the single-reference verification prompt (step 3). One reference per call. resp="$(curl -sS -w '\n%{http_code}' https://api.openai.com/v1/responses \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -d "$(jq -n --arg model "$ARS_CROSS_MODEL" --arg prompt "$PROMPT" \ --arg effort "${ARS_CROSS_MODEL_REASONING_EFFORT:-}" '{ model: $model, instructions: "You are a citation-verification assistant. Search the web before every verdict; never answer from memory. If you could not search, respond NOT_SEARCHED.", input: $prompt, tools: [{type: "web_search"}], temperature: 0.1 } + (if $effort == "" then {} else {reasoning: {effort: $effort}} end)')")" http="${resp##*$'\n'}"; body="${resp%$'\n'*}" # The grounding guard and source extraction are kept as canonical jq filters under # scripts/cross_model_verification/ so they are behavior-tested in CI (a from-memory verdict, a # malformed grounding index, etc.) and cannot silently stop failing closed. Reference them via # `jq -f` rather than inlining, so the doc and the test share one definition. GUARD=scripts/cross_model_verification if [ "$http" -lt 200 ] || [ "$http" -ge 300 ]; then # Transport/API failure (401/429/5xx, or curl's 000 on a network error) — NOT the same as # "searched but found nothing". Surface as a transport error so the consumer falls back to # single-model (see § Graceful Degradation); never relabel it NOT_SEARCHED, which would # imply a completed-but-ungrounded lookup. echo "CROSS-MODEL-ERROR: openai_http_$http" elif ! jq -e -f "$GUARD/openai_has_completed_web_search.jq" <<<"$body" >/dev/null; then echo "NOT_SEARCHED: no_web_search_call" # no search happened at all — discard the text else # A completed web_search_call proves *a* search ran, not that THIS reference's verdict # is supported by it. Emit the verdict text together with the url_citation annotations the # model attached; step 5 downgrades a VERIFIED with no citation to NOT_SEARCHED. text="$(jq -r -f "$GUARD/openai_text.jq" <<<"$body")" cites="$(jq -r -f "$GUARD/openai_sources.jq" <<<"$body")" printf '%s\nSOURCES: %s\n' "$text" "${cites:-(none)}" fi ``` ### Google Gemini (Gemini 3.1 Pro) The hosted grounding tool is `google_search` (REST uses snake_case; the JS SDK's `googleSearch` is the same tool). A grounded response carries `candidates[].groundingMetadata`; its absence means the model did not search. ```bash # PROMPT holds the single-reference verification prompt (step 3). One reference per call. resp="$(curl -sS -w '\n%{http_code}' \ "https://generativelanguage.googleapis.com/v1beta/models/${ARS_CROSS_MODEL}:generateContent?key=$GOOGLE_AI_API_KEY" \ -H "Content-Type: application/json" \ -d "$(jq -n --arg prompt "$PROMPT" '{ contents: [{parts: [{text: $prompt}]}], tools: [{google_search: {}}], generationConfig: {temperature: 0.1} }')")" http="${resp##*$'\n'}"; body="${resp%$'\n'*}" # Grounding guard + source extraction are canonical jq filters under scripts/cross_model_verification/ # (same rationale as the OpenAI block: behavior-tested, referenced via `jq -f`). The guard is # rederived from the source extractor: it passes iff the SAME extraction the source filter performs # yields at least one url AND the model issued a search (a non-empty webSearchQueries). So # guard-pass ⟹ a source is extractable — a groundingSupports linking to no valid chunk # (empty/negative/string/out-of-range/fractional index), the wrong candidate, or a non-string uri # all leave the extraction blank and fail the guard closed. See the .jq file headers for the full # contract. GUARD=scripts/cross_model_verification if [ "$http" -lt 200 ] || [ "$http" -ge 300 ]; then # Transport/API failure (401/429/5xx, or curl's 000) — surface as a transport error so the # consumer falls back to single-model (see § Graceful Degradation), not NOT_SEARCHED. echo "CROSS-MODEL-ERROR: gemini_http_$http" elif ! jq -e -f "$GUARD/gemini_is_grounded.jq" <<<"$body" >/dev/null; then echo "NOT_SEARCHED: no_grounding_support" # no search, or text not supported by it — discard else text="$(jq -r '.candidates[0].content.parts[]?.text // empty' <<<"$body")" cites="$(jq -r -f "$GUARD/gemini_sources.jq" <<<"$body")" printf '%s\nSOURCES: %s\n' "$text" "${cites:-(none)}" fi ``` > **Why `temperature: 0.1`:** reference existence/metadata checking is a deterministic factual task, so low temperature reduces run-to-run variance in the verdict. It is not a grounding control — the grounding guard above is what enforces an actual lookup. > **Reasoning effort (OpenAI only):** when `ARS_CROSS_MODEL_REASONING_EFFORT` is set, the payload passes it as `reasoning.effort`, making the effort a verification run uses visible and reproducible. When it is **unset, the field is omitted entirely and the provider's own default for the chosen model applies** — defaults differ across the lineup (GPT-5.6 documents `medium`; other ids carry their own), so forcing one value here would silently change behavior for existing setups. Citation lookup is search-bound, not reasoning-bound, so higher efforts mostly buy latency and cost; set the variable deliberately (never silently run at `xhigh`) if a run shows shallow search behavior. The value is passed through unvalidated (the API rejects unknown values): GPT-5.5 accepts up to `xhigh`, GPT-5.6 adds `max`. ### OpenAI-Compatible API (MiMo, DeepSeek, self-hosted) — ungrounded When `CROSS_MODEL_AVAILABLE=openai_compatible`, use the **Chat Completions API** at `ARS_OPENAI_COMPAT_BASE_URL`, authenticated with the dedicated `ARS_OPENAI_COMPAT_API_KEY`. These providers expose no hosted web-search tool, so there is **no grounding guard**. The handler therefore normalizes the verdict by invoking the canonical `normalize_compat_verdict.py` unit, which emits a single-line JSON object (`{"status","provider","context"}`): a positive `VERIFIED` is downgraded to `NOT_SEARCHED` (an ungrounded confirmation can never count as a grounded agreement), while a genuine rejection (`NOT_FOUND` / `MISMATCH`) passes through as a useful disagreement. The consumer reads `.status` only; the raw model text is JSON-escaped into `.context` as human-readable context and is **never** placed in a verdict slot the agreement counter parses — embedded newlines become literal `\n` inside the string, so a model response cannot inject a second status line. `PROMPT` holds the single-reference verification prompt from step 3. ```bash # ARS_OPENAI_COMPAT_BASE_URL is the API root INCLUDING /v1 (e.g. https://api.deepseek.com/v1). # Trailing slash is normalized so the endpoint is built exactly once — no double /v1. endpoint="${ARS_OPENAI_COMPAT_BASE_URL%/}/chat/completions" GUARD=scripts/cross_model_verification resp="$(curl -sS -w '\n%{http_code}' "$endpoint" \ -H "Authorization: Bearer $ARS_OPENAI_COMPAT_API_KEY" \ -H "Content-Type: application/json" \ -d "$(jq -n --arg model "$ARS_CROSS_MODEL" --arg prompt "$PROMPT" '{ model: $model, messages: [ {role: "system", content: "You are a citation-verification assistant. If you did not actually perform an external lookup, respond NOT_SEARCHED. Use NOT_FOUND only if you are confident no such record exists; MISMATCH if a field is wrong; VERIFIED only with a source URL/DOI."}, {role: "user", content: $prompt} ], temperature: 0.1 }')")" http="${resp##*$'\n'}"; body="${resp%$'\n'*}" if [ "$http" -lt 200 ] || [ "$http" -ge 300 ]; then # Transport/API failure (401/429/5xx, or curl's 000) — distinct from NOT_SEARCHED, so the # consumer falls back to single-model (see § Graceful Degradation), never an ungrounded verdict. echo "CROSS-MODEL-ERROR: openai_compatible_http_$http" else text="$(jq -r '.choices[0].message.content // empty' <<<"$body")" if [ -z "$text" ]; then echo "CROSS-MODEL-ERROR: openai_compatible_empty_response" else # Canonical normalization lives in scripts/cross_model_verification/normalize_compat_verdict.py # (behavior-tested in scripts/test_normalize_compat_verdict.py) and is INVOKED here rather than # re-implemented in bash — the same canonical-and-referenced pattern the first-party blocks use # with `jq -f`. It emits ONE line of JSON: {"status","provider","context"}. The consumer reads # .status only; raw model text is JSON-escaped in .context so it can never inject a second # status line (the producer/consumer anti-laundering contract holds at the output-format level). # VERIFIED -> status NOT_SEARCHED (ungrounded positive can never agree) # NOT_FOUND/MISMATCH -> status passes through (useful disagreement) # anything else/empty -> status NOT_SEARCHED (fail closed) printf '%s' "$text" | python3 "$GUARD/normalize_compat_verdict.py" fi fi ``` > **No grounding guard for compatible providers.** The grounding guard (an API-level > `web_search_call` / `groundingMetadata` trace) exists only for first-party OpenAI and > Gemini. A compatible provider cannot evidence a lookup, so its positive verdicts are > downgraded to `NOT_SEARCHED` and never count as agreement. Its rejections survive as > disagreements. The block emits a single-line JSON object (`{"status","provider","context"}`) > from `normalize_compat_verdict.py`, and the grounded-agreement count is computed solely from > its `.status` field — never from the raw text, which lives JSON-escaped in `.context`. > For the OpenAI-compatible block, read the verdict from the JSON `.status` field only > (e.g. `jq -r .status`); never grep the emitted line or `.context` for a verdict token — the > raw model text is preserved JSON-escaped in `.context` precisely so it cannot be mistaken for > a verdict. ### Detecting Available Models Agents should check at the start of a verification/review session: ```bash # Check which cross-model APIs are available # Requires: jq (for JSON parsing). Fallback: python3 -c "import sys,json; ..." if ! command -v jq &>/dev/null; then echo "WARNING: jq not installed. Cross-model API calls will use python3 fallback." fi case "${ARS_CROSS_MODEL_TRANSPORT:-api}" in codex) # Citation-integrity availability only. This does not make any DA/reviewer/ # judgment transport available. The detector shares auth/model/version logic # with the production verifier and honors a custom CODEX_HOME. python3 scripts/cross_model_codex_transport.py detect ;; api) if [ -n "$ARS_CROSS_MODEL" ]; then # PRECEDENCE: a first-party model id ALWAYS takes the grounded route, even if # ARS_OPENAI_COMPAT_BASE_URL is set. This prevents a grounded->ungrounded downgrade. ANY gpt-* # id (not just today's gpt-5.5/gpt-5.4) and any gemini-* id route grounded, so a future # first-party release keeps the grounded path instead of silently falling through to the # ungrounded compatible branch. The compatible path is reachable only for a model id that # matches no first-party prefix, and only when its dedicated opt-in env vars are both present. # OPENAI_BASE_URL is never read. # ID STATUS is a separate axis from routing (#518): routing answers "which provider # endpoint", the allowlist answers "is this id known-good". An unlisted gpt-*/gemini-* id # still routes grounded (never falls through to the ungrounded compatible branch) but is # announced as unlisted so nobody trusts results from a typo'd or made-up id the API has # never accepted. Applies to first-party routes only — compatible-route ids are # user-declared and carry no allowlist. id_status() { case " gpt-5.5 gpt-5.5-pro gpt-5.4 gpt-5.4-pro gemini-3.1-pro-preview " in *" $1 "*) echo "validated"; return ;; esac # gpt-5.6-sol: validated for the codex subscription citation transport # (2026-08-19 bakeoff); provisional HERE because this allowlist gates the # first-party API route, which has no recorded bakeoff run. case " gpt-5.6-sol " in *" $1 "*) echo "provisional"; return ;; esac echo "unlisted" } announce_id_status() { status="$(id_status "$ARS_CROSS_MODEL")" echo "CROSS_MODEL_ID_STATUS=$status" case "$status" in provisional) echo "NOTE: $ARS_CROSS_MODEL is provisional — endpoint support confirmed, ARS-specific behavior unvalidated (see Supported Models). Run scripts/cross_model_smoke_test.sh before relying on it." ;; unlisted) echo "WARNING: $ARS_CROSS_MODEL matches a first-party prefix and routes grounded, but is NOT a known-good id — the API may reject it. Check the id, or run scripts/cross_model_smoke_test.sh before trusting results." ;; esac } case "$ARS_CROSS_MODEL" in gpt-*) if [ -n "$OPENAI_API_KEY" ]; then echo "CROSS_MODEL_AVAILABLE=openai"; announce_id_status else echo "WARNING: ARS_CROSS_MODEL=$ARS_CROSS_MODEL but OPENAI_API_KEY is not set" fi ;; gemini*) if [ -n "$GOOGLE_AI_API_KEY" ]; then echo "CROSS_MODEL_AVAILABLE=google"; announce_id_status else echo "WARNING: ARS_CROSS_MODEL=$ARS_CROSS_MODEL but GOOGLE_AI_API_KEY is not set" fi ;; *) # Unrecognized id: only an explicit, credential-isolated opt-in enables the ungrounded # OpenAI-compatible path. Both the base URL AND the dedicated key are required; the # standard OPENAI_API_KEY is NEVER sent to a third-party endpoint (see Credential # isolation in the API Call Patterns section). if [ -n "$ARS_OPENAI_COMPAT_BASE_URL" ] && [ -n "$ARS_OPENAI_COMPAT_API_KEY" ]; then echo "CROSS_MODEL_AVAILABLE=openai_compatible" elif [ -n "$ARS_OPENAI_COMPAT_BASE_URL" ]; then echo "WARNING: ARS_OPENAI_COMPAT_BASE_URL is set but ARS_OPENAI_COMPAT_API_KEY is not — refusing to send another provider's key. Set ARS_OPENAI_COMPAT_API_KEY." echo "CROSS_MODEL_AVAILABLE=none" else echo "WARNING: ARS_CROSS_MODEL=$ARS_CROSS_MODEL is not a recognized model. First-party grounded route: any gpt-* id (e.g. gpt-5.5, gpt-5.5-pro, gpt-5.6-sol, legacy gpt-5.4*) or gemini-* id (e.g. gemini-3.1-pro-preview). For an OpenAI-compatible provider set ARS_OPENAI_COMPAT_BASE_URL + ARS_OPENAI_COMPAT_API_KEY and use that provider's model id (must not match a gpt-*/gemini-* prefix, or it takes the grounded first-party route instead)." echo "CROSS_MODEL_AVAILABLE=none" fi ;; esac else echo "CROSS_MODEL_AVAILABLE=none" fi ;; *) echo "CROSS-MODEL-ERROR: invalid ARS_CROSS_MODEL_TRANSPORT selector" echo "CROSS_MODEL_AVAILABLE=none" ;; esac ``` If the API route is selected and `ARS_CROSS_MODEL` is set but the corresponding API key is missing or the model name is unsupported, the agent should warn the user and proceed with single-model verification. If the citation-only `codex` route is selected, consume the detector's closed status instead; an invalid transport selector is a visible configuration error and never falls through to an API route. ### Promotion Bakeoff (provisional → validated) The run that flips a provisional id (today: `gpt-5.6-sol`) to validated is defined here so a future promotion argues against numbers, not vibes (#518). Validation and recommendation are separate axes. (2026-08-19, #783: the recommendation moved to the current generation on lifecycle grounds — GPT-5.5 was superseded — ahead of validation; that flip carries no measurement claim. This bakeoff remains the only route to `validated`, and any claim of measured parity or superiority still requires the run below.) > **Recorded run (2026-08-19/20, #787 — codex-transport variant).** The procedure below was executed over the #630 ChatGPT-subscription citation transport (entry gate: `scripts/cross_model_smoke_test_codex.sh` PASS for baseline and candidate; measure analogues: grounding evidence = receipt `searched`, measure 4 = zero fail-closed receipt-guard misfires). All five measures passed in the counterbalanced gate fleet, with superiority on measures 2 (fabrication recall) and 5 (latency) and a tie on measure 1 — see `audits/bakeoff-gpt-5-6-sol-codex-2026-08-19.md` (probe set `evals/bakeoff/2026-08-19-gpt-5-6-sol-codex/`, sha256 in the report). The result is **transport-qualified**: `gpt-5.6-sol` is validated for the subscription citation transport; it remains provisional on the first-party API route, whose jq grounding guards that run did not exercise. A scored fleet is bound to its preregistered frozen instrument; later instrument hardening that validates only surfaces outside every consumed path applies from the next fleet and does not retroactively invalidate a recorded gate result (boundary rationale in the run report's Instrument-freeze decision record). An API-route run requires a FRESH probe set under the #789 sealed-preregistration protocol below — the 2026-08-19 set's labels are public, so reusing it would expose a live-search run to answer-key retrieval. - **Entry gate:** `scripts/cross_model_smoke_test.sh` passes against the candidate id. - **Probe-set precondition — sealed preregistration (#789; both API and codex transports):** every future gate run uses `scripts/check_promotion_bakeoff_preregistration.py` and the closed `shared/contracts/cross_model/promotion_bakeoff_sealed_commitment.schema.json` + `shared/contracts/cross_model/promotion_bakeoff_sealed_reveal.schema.json` contracts. A bakeoff against an ad-hoc/unsealed set, or one revealed before its fleet completed, is not a gate result. 1. **Prepare privately.** Build one `ars-bakeoff-probe-set/1.0` fixture with 30 references — 20 real (10 easy DOI-keyed + 10 hard preprint/DOI-less/non-English) and 10 synthetic plausible fabrications. Real rows carry resolver-confirmed DOI/arXiv/URL ground truth; fabricated rows carry a fresh negative-check witness. Keep the labeled file outside Git (an untracked canonical path is permitted, but a private path is safer). `python3 scripts/check_promotion_bakeoff_preregistration.py prepare --campaign-id --probe-set ` validates the shape and historical non-reuse, then prints a closed commitment containing only the campaign id, LF-normalized file sha256, fixed row count, and aggregate composition — never a row, label, ground-truth identifier, fixture path, or free-text escape hatch. 2. **Commit and publish the seal before any call.** Save that output as `evals/bakeoff//sealed_commitment.json` in a dedicated commit whose only changed path is that file; do not stage the fixture or reveal carrier. Push it, wait until the commit and its passing CI result are publicly reachable, record the immutable commit permalink, then run `python3 scripts/check_promotion_bakeoff_preregistration.py preflight --commitment evals/bakeoff//sealed_commitment.json --probe-set `. A local commit, timestamp, or later ancestry proof is not a substitute for this public-before-fleet witness. 3. **Run while sealed.** Run the counterbalanced baseline/candidate fleet with the fixture local. No scored call may precede the successful preflight; neither `probe_set.json` nor `sealed_reveal.json` may enter Git while any fleet call remains pending. 4. **Reveal after the fleet.** Once all calls have reached terminal retained rows, place the unchanged fixture at `evals/bakeoff//probe_set.json`; run the checker's `make-reveal` command to produce `evals/bakeoff//sealed_reveal.json`; add those two files together in one later commit. `verify-reveal` (one campaign) or `verify-tree` (CI, all campaigns) fails closed on digest/composition drift, duplicate JSON keys, symlink/path substitution, non-isolated or rewritten commitments, non-ancestor/same-commit seals, probe/reveal introduction drift, or post-reveal mutation. Squash/cherry-pick copies are accepted only when they descend the same seal and carry the identical bound probe/reveal lifecycle; the receipt exposes every qualifying introduction in `reveal_copy_git_commits` rather than hiding source-ref copies. 5. **Never reuse a published answer key.** Once labels appear in any Git version, those exact probe bytes are retired permanently and every later gate gets a fresh fabrication pool. The verifier scans every historical version of every `evals/bakeoff/**/probe_set.json`; a fabricated reference remains reused even if its id, context, case, Unicode width, spacing, or punctuation changes. Previously used real references may remain, but no previously labeled reference may enter the new fabricated pool. The 2026-08-19 fixture is the sole explicitly grandfathered unsealed artifact: its canonical path and LF-normalized SHA-256 are pinned, its blob bytes and regular-file mode must remain immutable across reachable history, and it remains part of the published-history scan. Verification requires a complete non-shallow local history and fails closed when a referenced historical object cannot be read. The reveal verifier proves byte binding, composition, Git immutability/order, and detectable historical non-reuse. It cannot prove when a commit became visible on a remote or when an external call ran. The run report therefore MUST record the public commitment permalink and CI result, successful preflight output, fleet start/end bounds, final verifier receipt, and—for every listed reveal-copy commit in `reveal_copy_git_commits`—a public permalink plus evidence that it first became publicly reachable only after the fleet ended. A copy published before or during the fleet invalidates the gate even when a later squash commit is clean. Missing remote/timing evidence makes the fleet exploratory, not a gate result. - **Procedure:** run the baseline (`gpt-5.5`) and the candidate the same day, one call per reference, 3 repeats. Per-reference verdict = the verdict returned by ≥ 2 of 3 repeats; if no verdict reaches 2 (a 1–1–1 split), the reference is **indeterminate** and scored conservatively against the model that produced it — a miss for recall (measure 2), a false disagreement for measure 3. Grounded-search completion (measure 1) is computed per call, so ties don't apply. - **Non-inferiority thresholds — all five must pass:** 1. **Grounded-search completion rate** (share of calls returning grounding evidence) ≥ baseline − 5 pp. 2. **Citation-mismatch recall** on the 10 fabrications (share flagged `NOT_FOUND`/`MISMATCH`) ≥ baseline − 5 pp AND ≥ 80% absolute. 3. **False-disagreement rate** on the 20 real references (share incorrectly flagged `NOT_FOUND`/`MISMATCH`) ≤ baseline + 5 pp. 4. **jq-guard shape stability:** zero guard misfires attributable to response-shape change across all calls (hard requirement — a shape change that trips the fail-closed guards disqualifies regardless of the other measures). 5. **p95 latency** ≤ 2× baseline. - **Outcome — validation, plus a separate claim rule:** - **All five pass → `provisional` becomes `validated`** (the id-status allowlist and the Supported Models note update; a promotion PR records the run under `audits/` with the probe-set hash). Non-inferiority earns trust, nothing more. - **A measured-superiority claim additionally requires observed superiority on at least one of the five measures, with no inferiority elsewhere, named explicitly in the promotion PR.** An operational benefit (cost, latency, capability) is a recommendation-policy argument, never a measurement claim. A candidate that merely scraped under every tolerance (−5 pp grounding, −5 pp recall, +5 pp false disagreements, 2× latency) is validated, nothing more. (Recommendation itself follows generation currency — see the 2026-08-19 note above — but stays caveated as provisional/unmeasured until this run passes.) - Any fail → the id stays provisional; the results are still recorded. Web-search results vary day to day; the 3-repeat majority verdict and same-day paired runs are what make the comparison fair. Thresholds are the #518 spec's choice and are tunable in a future spec without redesigning the procedure. ## Cost Considerations Cross-model verification adds API costs from the second provider: | Scenario | Additional Calls | Estimated Additional Cost | |----------|-----------------|--------------------------| | Integrity verification (risk-stratified: HIGH-IMPACT — and at Stage 4.5 NEW-CHANGED — 100% uncapped + sampled remainder, min 3 / max 10; **one call per reference**) | worked example: 60 refs, 12 high-impact → 12 + 5 = 17 calls. No fixed upper bound — a results-dense paper approaches all references | ~$1.35-2.95 (the example; scales linearly with calls) | | DA cross-check (1 per checkpoint, 3 checkpoints) | 3 calls | ~$0.30-0.55 | | Blind disagreement checkpoints (design freeze + final editorial decision, 1 structured-decision call each; editorial repeats on re-review) | 2-3 calls | ~$0.20-0.55 | | **Full pipeline (the worked example)** | **~22-23 calls** | **~$1.85-4.05 — no fixed ceiling; grows with the high-impact / new-changed count** | These are rough estimates based on GPT-5.5 pricing ($5/1M input, $30/1M output) and typical prompt sizes; GPT-5.5 Pro runs ~6× higher ($30/1M input, $180/1M output). GPT-5.6 Sol bills at the same standard rates as GPT-5.5 ($5/1M input, $0.50/1M cached input, $30/1M output); its pro mode keeps those rates but performs more model work per request, so total tokens (and latency) rise instead of the unit price. One-call-per-reference (rather than batching) is a deliberate cost-for-provenance trade: it is the only way the grounding-evidence check maps 1:1 to each verdict. Web-search-tool calls also cost more than plain completions. ## Limitations 1. **Does not solve frame-lock fully.** All major LLMs share substantial training data. Cross-model catches different surface errors but may share deep structural biases. 2. **API latency.** Cross-model calls add 2-5 seconds per call, plus web-search round-trip time. With one call per reference (no batching) and a web-search tool, a risk-stratified integrity selection (uncapped HIGH-IMPACT plus the capped RANDOM sample at Stage 2.5; uncapped HIGH-IMPACT + NEW-CHANGED plus the capped CONTROL sample at Stage 4.5) can add several minutes on a results-dense paper; the calls can be issued concurrently to bound wall-clock time. 3. **Response format differences.** Different models structure responses differently. The agent must parse varied formats — keep verification prompts simple and structured to minimize parsing issues. 4. **Cost scales with paper size.** Longer papers with more references = more cross-model calls. ## Graceful Degradation If cross-model verification fails **at the transport level** (API error, rate limit, key expired): - Log the failure: `[CROSS-MODEL-ERROR: reason]` - Continue with single-model verification — never block the pipeline on cross-model failure - Include a note in the report: "Cross-model verification was configured but unavailable for this run. Results are single-model only." A `NOT_SEARCHED` result is **not** a transport failure and is handled differently. It means the call succeeded but the model could not (or did not) ground the lookup, so its verdict carries no evidence. Do not fall back to single-model and do not treat it as agreement: record the reference as `NOT_SEARCHED` in the results table, count it separately from agreements/disagreements, and surface it for re-run or human review. The distinction matters — a transport failure means "we have no cross-model opinion"; a `NOT_SEARCHED` means "the cross-model gave an opinion we have decided not to trust as a confirmation."