Spoolis docs
Bring your own judge
Spoolis does not require you to replace your evaluator. Your CI, deterministic tests, buyer-owned checks, marketplace evaluator, coding-agent reviewer, external verifier, or a named human can stay the judge. Spoolis turns that judgment into a portable Outcome.
Why this exists
Most serious systems already know how they want to judge their own work. Agent runtimes and orchestrators increasingly define each step by the outcome it must produce and check that outcome before the next step runs. Keep that: your orchestrator can stay the judge. A runtime's judgment is local by default, and inside your own workflow that is exactly right. Spoolis is useful at the acceptance boundary: the point where that result needs to leave the system and be trusted somewhere else, by billing, a deploy pipeline, a retry workflow, a marketplace, or another agent, and where authority, provenance, and reuse become separate questions from whether the step succeeded.
The flow is always the same:
your evaluator → signed or structured result → Spoolis → portable Outcome → billing / runtime / deploy / marketplace / another agent
Spoolis records which evaluator produced the result, binds it to the agreement and evidence, preserves partial, uncertain, and earned value, and signs the Outcome so any number of downstream consumers can verify it independently.
Declare the judge on a condition
A condition judged outside Spoolis uses verification_method: "third_party" with an external_judge declaration. The declaration names the evaluator, its kind (buyer_owned, marketplace_evaluator, external_service, ai_evaluator, zk_verifier, or human_arbitration), and the proof requirement: signed (with the evaluator's public key), hashed_ref, or declared.
{
"description": "Billing telemetry stays within the agreed error threshold",
"required": true,
"verification_method": "third_party",
"external_judge": {
"evaluator_id": "acme-billing-telemetry",
"kind": "buyer_owned",
"proof_requirement": "signed",
"public_key": "-----BEGIN PUBLIC KEY-----..."
}
}The evaluator's result arrives as evidence with provenance: "external_tool_result" carrying the result object: the verdict, per-condition rows, optional per-unit verdicts, and the proof. With proof_requirement: "signed", Spoolis verifies the signature over the canonical result payload and refuses to bind an unverified result to the Outcome; it degrades to uncertain, never to a silent pass.
The wire: submit your judge's result
Three calls carry the whole exchange, all with a production API key (full or counterparty scope).
1. Read the binding values. Every Spool read returns a verification_binding block: the agreement_hash, the evidence_root over the evidence submitted so far, and, on a unitized Spool, the agreed unit_ids your result must use.
curl https://spoolis.com/api/v1/spools/$SPOOL_ID \
-H "authorization: Bearer $SPOOLIS_API_KEY"
# response includes:
# "verification_binding": { "agreement_hash": "<64 hex>", "evidence_root": "<64 hex>", "unit_ids": ["unit-01", "unit-02", ...] }2. Submit the result. POST /api/v1/spools/{id}/external-result. The evaluator identity must match the declaration, the binding must echo the values from step 1, the overall verdict must agree with the per-condition rows, and unit ids must be the agreed ones. Resubmitting an identical result is idempotent.
curl -X POST https://spoolis.com/api/v1/spools/$SPOOL_ID/external-result \
-H "authorization: Bearer $SPOOLIS_API_KEY" \
-H "content-type: application/json" \
-d '{
"evaluator": { "id": "acme-billing-telemetry", "kind": "buyer_owned" },
"verdict": "pass",
"condition_results": [
{ "condition_ref": "cond_<from the Spool>", "result": "pass" }
],
"units": [
{ "id": "unit-01", "verdict": "pass" },
{ "id": "unit-02", "verdict": "fail", "reason": "threshold exceeded" }
],
"binding": { "agreement_hash": "<from verification_binding>", "evidence_root": "<from verification_binding>" },
"proof": { "kind": "signature", "ref": "<base64 signature over the canonical result>" },
"observed_at": "2026-09-09T12:00:00.000Z"
}'3. Run verification. POST /api/v1/spools/{id}/verify evaluates every condition, applies your judge's result to its condition, and issues the signed Outcome.
Rules worth knowing before you integrate:
proof_requirement: "signed"verifies the signature against the public key declared in the agreement; a failed or missing proof resolves to uncertain, never to a pass.on_missingon the declaration decides what happens when your judge never reports:uncertain(default) orfail.- A score-carrying judge declares
score_mapping(schema_id,quality_gte,consensus_gte) and submitsscored_units; Spoolis maps scores to verdicts under the agreed thresholds, and an unknown schema resolves to uncertain. - Submitting evidence after the result changes the
evidence_root, so readverification_bindinglast, after all evidence is in.
Or let the SDK drive the lifecycle
@spoolis/accept wraps the whole sequence, including creating the Spool, submitting evidence, reading the binding, calling your judge, submitting its result, and running verification:
import { accept } from '@spoolis/accept'
const outcome = await accept({
policy: { criteria: 'Every unit passes the telemetry check', units: 10, unitValueCents: 100 },
evidence: { type: 'dataset', rows, provenance: 'api_response' },
judge: {
meta: { evaluator_id: 'acme-billing-telemetry', kind: 'buyer_owned', proof_requirement: 'declared' },
run: async ({ unitIds, evidence }) => ({
units: unitIds.map((id) => ({ id, pass: myEvaluator(id, evidence) })),
}),
},
}, { baseUrl: 'https://spoolis.com', apiKey: process.env.SPOOLIS_API_KEY })The judge is { meta, run }: meta is the same declaration the agreement pins, and run returns { pass } or per-unit { units: [{ id, pass }] }. A judge that throws or returns a malformed result under declared proof resolves to uncertain rather than failing the run. Score-carrying judges add schemaId to meta and acceptIf thresholds to the policy.
Example A: coding agent with CI and buyer-owned telemetry
Agreement: migration complete, tests pass, billing stays healthy. Judges: CI for the tests, the buyer's own telemetry check for billing. If CI passes but the telemetry condition fails, the Outcome is partial or fail, the deploy consumer refuses to proceed, and a remediation agent receives exactly the failed condition.
The consumers gate with requireOutcome:
import { requireOutcome } from '@spoolis/receipt-verifier'
const gate = await requireOutcome(receipt, {
minimum_status: 'pass',
evidence_requirements: 'satisfied',
}, { trustSet, environment: 'production' })
if (!gate.ok) {
// gate.reasons names why; the receipt's condition_results name
// which condition failed, so remediation gets a precise target.
blockDeploy(gate.reasons)
}Example B: marketplace evaluator, 8 of 10 units
The marketplace keeps its evaluator. It submits per-unit verdicts; Spoolis standardizes the result: 8 accepted, 2 rejected, $8.00 earned, signed. Billing pays what was earned, and the workflow retries the rejected units. A partial batch has result: "partial", so a consumer that should act on it gates on units and money, not on minimum_status: "pass":
const billing = await requireOutcome(receipt, {
minimum_status: 'partial',
min_accepted_units: 1,
}, { trustSet })
if (billing.ok) payExactly(billing.earned) // '8.00'This ran for real: the archived two-consumer demo receipt is one marketplace-judged Outcome that a billing system and a retry workflow each consumed independently, recording two consumption events against it.
Example C: prerequisite results for a consequential action
An agent runtime can require prerequisites before a consequential action: the fee is known, the terms were retrieved, an explicit confirmation exists where policy requires one. If the runtime chooses to use Spoolis as the result layer, each prerequisite becomes a condition, the runtime's own checks judge them, and the signed Outcome records whether they were satisfied at decision time.
Spoolis does not own user intent or authorization. It records whether declared prerequisites held, for the runtime and for anything auditing the action later.
The trust boundary
Spoolis proves: which evaluator produced the result, what agreement and evidence it was bound to, what verdict was signed, and how earned value was derived. Spoolis does not prove the external judge was substantively correct, or that every outside-world fact was true. The public receipt page states this boundary on every Outcome, and consumers should read condition_results[].method and the evaluator identity before deciding how much weight to give a verdict.
What a judgment binds
An Outcome's authority depends on who agreed to the judge, not on the signature alone.
- Unilateral. The buyer sets the criteria, supplies its own evidence, and runs its own agreed judge. The result is authoritative to the buyer: its billing, renewals, and procurement can act on it, and the seller can verify it independently without an account. It does not bind the seller, because the seller never agreed to the rules.
- Agreed. The seller accepts the criteria once, before the work. Both sides hold the same signed result, computed under rules they both agreed to.
- Gated. Settlement itself waits on the verdict. The seller's involvement is accepting the agreement and receiving what was earned.
Spoolis authenticates and preserves the judgment. It does not make an arbitrary judge trustworthy; it makes a dishonest verdict evidenced and, for deterministic checks, reproducible, so a disputed result is checkable rather than a competing anecdote.
Routing disposition vs acceptance state
Two different questions hide inside "what was the outcome." Your workflow's question is routing: which named path should the run take next (eligible, ineligible, needs review, escalate). The acceptance question is economic and universal: which units counted, which did not, which are unresolved, and what was earned. Spoolis deliberately records only the second, as accepted, rejected, or uncertain per unit. Your dispositions stay yours: the consuming system applies its own policy to the same result, and a consumption event can record which policy it applied. Keeping the two apart is what lets one signed result serve systems with different policies.
The judge-swap guarantee
The claim that the judge is a parameter is a passing test, not positioning. Our test suite swaps two different agreed judges under one agreement, a signed external score-carrying judgment and a local evaluator, and asserts the economically relevant Outcome is identical: same per-unit verdicts, same counts, same earned value, same fields a payment consumer reads. Judge identity stays in provenance and never leaks into the economic projection. Tampered signatures, unknown schema versions, and missing units all resolve to uncertain, never to a silent pass. If you integrate against the Outcome, a judge change on the other side of the seam does not change your contract.
Calling it
Everything above works over the plain HTTP API, the hosted MCP (anonymous sandbox needs no account), and the SDKs. The quickstart covers the create, evidence, verify sequence; the sandbox scenario external_judge_two_consumers runs the whole marketplace example end to end in one call. Note the split: the scenario below runs with a free sandbox token, while external-result itself is a production route and needs a production API key.
curl -X POST https://spoolis.com/api/sandbox/scenarios/run \
-H "authorization: Bearer $SANDBOX_TOKEN" \
-H "content-type: application/json" \
-d '{"scenario": "external_judge_two_consumers"}'What to use this for
- Cross-system handoffs, where the judging system and the acting system are different.
- Partial results and earned value that a boolean would destroy.
- Multiple independent consumers of one judgment.
- Auditability: who judged what, against which agreement, on what evidence.
- Retry and remediation that needs to know exactly which conditions or units failed.
What not to use this for
- Ordinary API calls whose success is self-evident to the caller.
- Local-only boolean checks that never leave the system.
- Replacing CI or your evaluator. Keep them; they are the judge.
- Universal authorization or policy enforcement. Spoolis records outcomes; it does not grant authority.
- Generic model benchmarking. An Outcome attests one agreement, not a leaderboard.