The Judge
The Judge asks a panel of models the same question over the same labeled
evidence and signs the outcome: which models were asked, what evidence
they were all given, and how each one voted. Every vote is itself a normal
metered chat completion under your key, so it carries its own signed
inference receipt — and that receipt's context.root equals the verdict's
evidence.root, tying every vote to the same inputs.
Use it where a decision needs a portable, checkable record rather than a trusted party: prediction-market resolution, escrow disputes, DAO proposals, insurance claims, "did the agent do the right thing" reviews.
What a verdict proves, and what it does not
This section is the product. The same sentence travels inside the signed
payload as trust_model, so the limit cannot be separated from the proof.
- Proves: the panel (model ids, and the engine that actually served each one), the evidence root (an order- and content-sensitive sha256 that anyone holding the same texts recomputes), each juror's vote and stated confidence, the decision rule, and that the Sable gateway signed all of it. Each vote is backed by a separately signed inference receipt carrying the same evidence root.
- Does not prove: that the decision is true. Models can be wrong together. The evidence is whatever the requester chose to submit, and a rationale is a model's account of itself, not an audit. Treat a verdict as a verifiable record of a process, and decide for yourself how much weight that process deserves.
Quickstart
curl https://api.buildsable.com/v1/judge/verdicts \
-H "Authorization: Bearer $SABLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"question": "Did the shipment clear customs before 1 June 2026?",
"options": ["yes", "no"],
"evidence": [
{"label": "customs-release", "text": "Release notice, stamped 29 May 2026 …"},
{"label": "carrier-log", "text": "Carrier scan history …"}
],
"rubric": "Prefer primary documents over summaries.",
"publish": true
}'{
"id": "vrd_7f1c…",
"decision": "yes",
"unanimous": false,
"dissent": 1,
"method": "majority-v1",
"tier": "standard",
"jurors": [
{
"model": "sable",
"engine": "anthropic/claude-opus-5",
"vote": "yes",
"confidence": 0.86,
"rationale": "The release notice is a primary document dated 29 May …",
"receipt_request_id": "1c2e…",
"error_class": null,
"cost_micro_usd": 2140
},
{ "model": "sable-deepseek-r1", "vote": "yes", "confidence": 0.7, "…": "…" },
{ "model": "sable-gemini-2.5-pro", "vote": "no", "confidence": 0.55, "…": "…" }
],
"evidence": { "count": 2, "root": "9b2f…" },
"receipt": "eyJ2IjoxLCJraW5kIjoidmVyZGljdCIs…",
"signature": "0x…",
"signer": "0x…",
"hash": "e4a1…",
"cost_micro_usd": 3910,
"published": true,
"public_url": "https://buildsable.com/j/vrd_7f1c…",
"trust_model": "attested: signed by the Sable gateway over which models were asked …",
"created_at": "2026-09-06T10:12:04Z"
}
The question and the rationales come back once, in this response. Unless you published, they are not stored anywhere (see below).
Request
POST /v1/judge/verdicts — key-authed (Authorization: Bearer sk-sable_…).
Agents call it directly.
| Field | Type | Notes |
|---|---|---|
question | string | Required. Up to 4,000 characters. |
options | string[] | 2 to 8 short labels (≤64 chars each). Default ["yes","no"]. abstain and undecided are reserved. |
evidence | {text, label?}[] | 0 to 32 items, ≤32 KiB each, ≤256 KiB total. Fingerprinted in-frame; the texts are never stored. |
models | string[] | 1 to 5 Sable model ids. Default: the deployment's SABLE_JUDGE_MODELS, else sable, sable-deepseek-r1, sable-gemini-2.5-pro, filtered to models this deployment can actually serve. An unknown, embedding, or unservable model is a 400 naming it. |
rubric | string | Optional judging instructions (≤2,000 chars), appended to every juror's system prompt and fingerprinted onto the verdict as rubric_fp. |
sable_privacy_tier | "standard" | "confidential" | Default: the key's tier. On confidential, every juror runs through the attested route and each juror receipt carries the attestation; every model on the panel must then be confidential-capable (a model listed with privacy_tier: confidential, or a flagship id, which dials onto one). A deployment without an attested backend refuses with 400. |
publish | boolean | Store the question and rationales sealed and expose the public page. Default false. |
sable_run_id | string | Chain the verdict receipt into an agent run, like any other receipt. |
Each juror sees a strict system prompt (judge instructions plus your rubric), then one user turn: the question, the numbered options, and the labeled evidence. Jurors run concurrently at temperature 0 with a 700-token ceiling, and each is an ordinary in-process chat completion under your key — prepaid credit and holds, spend caps and windows, policy-as-code, guardrails, the circuit breaker, and receipts all apply with no exceptions.
How votes are read
A juror is asked to reply with only {"vote", "confidence", "rationale"}.
Parsing is lenient: the first JSON object in the reply is used; the vote is
matched to an option case-insensitively (a 1-based index also works); a bare
reply that simply is an option counts. Anything else is recorded as
abstain with error_class: "unparseable_vote". A juror whose call fails
— refused for credit (402), out of scope (403), blocked by a guardrail
(422), rate-limited, or an upstream error — abstains with a fixed class,
and the panel decides on the rest. A verdict mints as long as at least one
juror voted; an all-abstain panel is an error (402 if every juror was
refused for credit, otherwise 502), and nothing is signed.
The decision rule
method: "majority-v1" — the option with the most non-abstain votes wins.
unanimous is true when every juror who voted agreed (abstentions do not
break unanimity). dissent counts the votes not cast for the winner. A tie
among the leaders is "undecided", never a coin flip.
Response and the signed payload
The receipt is the base64url canonical JSON of the verdict payload,
signature is its EIP-191 secp256k1 signature, signer is the gateway's
signing address (GET /v1/receipts/pubkey). The payload is content-free:
{
"v": 1,
"kind": "verdict",
"id": "vrd_…",
"question_fp": "<sha256 prefix of the question>",
"options": ["yes", "no"],
"evidence": { "count": 2, "root": "<sha256 over ordered sha256s>" },
"rubric_fp": "<sha256 prefix, when a rubric was given>",
"jurors": [
{
"model": "sable",
"engine": "anthropic/claude-opus-5",
"vote": "yes",
"confidence": 0.86,
"rationale_fp": "<sha256 prefix of the rationale text>",
"receipt_request_id": "<that juror's own signed inference receipt>"
},
{ "model": "…", "vote": "abstain", "error_class": "upstream_error" }
],
"decision": "yes",
"unanimous": false,
"dissent": 1,
"method": "majority-v1",
"tier": "standard",
"trust_model": "attested: … It proves the process, never that the decision is true …",
"created_at": "2026-09-06T10:12:04Z"
}
evidence.root is sha256(sha256_hex(item_0) ‖ sha256_hex(item_1) ‖ …) over
the evidence texts in order — the same construction as
sable_context, because it is the
same call: each juror request declares the evidence as sable_context, so
fetch any juror's receipt (GET /v1/receipts/{receipt_request_id}) and its
context.root matches. rationale_fp lets a rationale published later be
checked against the verdict that was signed at the time.
cost_micro_usd on the response is the sum of the jurors' receipted costs
plus the optional fixed fee. There is no charge for the verdict itself unless
the operator sets one.
Endpoints
| Method | Path | Auth | What it does |
|---|---|---|---|
| POST | /v1/judge/verdicts | API key | Ask the panel, sign and store the verdict. |
| GET | /v1/judge/verdicts | API key | Content-free rows: decision, votes, models, cost, published/anchored flags. ?limit= up to 200. |
| GET | /v1/judge/verdicts/{id} | API key | Owner detail: juror records, receipt, signature, anchor. Never the question or rationales. |
| POST | /v1/judge/verdicts/{id}/publish | API key | Publish. Idempotent if already published; otherwise the body must re-supply the content (see below). |
| POST | /v1/judge/verdicts/{id}/unpublish | API key | Destroy the sealed content immediately. The content-free record and its signature remain. |
| GET | /v1/judge/public/{id} | Public | A published verdict with its question, rationales, receipt, signature and anchor. 404 unless published. Rendered at /j/{id}. |
Publication and the privacy contract
The question and the rationales are content, and Sable's rule for content is
that it is never persisted. A verdict therefore stores fingerprints and votes,
and returns the text once. Publishing is the one deliberate exception, and it
follows the same posture as hosted agents and Relay: the question, options and
rationales are AES-GCM-sealed with the master key, opened in-frame only to
answer the public endpoint, never logged, and nulled the instant you
unpublish. An unpublished verdict is 404 to the world — indistinguishable
from one that never existed.
Because nothing was stored, publishing after the fact means re-supplying the text:
POST /v1/judge/verdicts/vrd_…/publish
{
"question": "Did the shipment clear customs before 1 June 2026?",
"rationales": [
{ "model": "sable", "rationale": "The release notice is a primary document …" }
]
}
The gateway checks the question against the signed question_fp and each
rationale against that juror's signed rationale_fp; anything the panel did
not actually see is refused with 400. A publish with no body on a verdict
that was minted unpublished answers 409 and says why. Rationales are
optional — a verdict can be published with the question alone.
Anchoring
Each verdict's hash (sha256 of the exact signed bytes) joins the judge
anchor stream. On the shared schedule, unanchored hashes are folded into a
batch root and published as a Solana memo sable-judge:v1:<root> (and to any
other configured anchor backend). The public verdict carries
anchor: {id, solana_sig, status, anchored_at} once included, and null
before — the page says "awaiting anchor" rather than implying one. A batch is
recomputable by anyone from its ordered hash list at
GET /v1/vault/anchors/{id}.
On-chain use
The signature is EIP-191 over the canonical payload bytes, so a contract can
check it with ecrecover against the published signer and read the decision
from the payload the caller submits. Note: no Sable contract is deployed;
this is a description of what the signature format makes possible, not of a
resolver you can call today. If you build one, pin the signer address from
GET /v1/receipts/pubkey and remember what the verdict does and does not
prove.
Configuration
| Variable | Default | Meaning |
|---|---|---|
SABLE_JUDGE_MODELS | unset | CSV of Sable model ids for the default panel. Unset ⇒ sable,sable-deepseek-r1,sable-gemini-2.5-pro, filtered to what the deployment can serve. |
SABLE_JUDGE_FEE_MICRO_USD | 0 | Optional fixed fee per verdict, metered as one usage_events row of kind verdict on top of the jurors' own inference. 0 records no fee row at all. |
Limits
- Up to 5 jurors, 8 options, 32 evidence items (≤32 KiB each, ≤256 KiB total), 4,000-character question, 2,000-character rubric.
- Rationales are truncated to 600 characters; confidence is clamped to
[0, 1]. - Jurors are not streamed and there is no retry on a juror's failure: the abstention is recorded as the honest outcome of that call.
- The confidential tier needs an attested backend on the deployment and a confidential-capable panel; the verdict does not carry an attestation of its own — each juror receipt does.