ReviewProtocol schema
Generated from the canonical schema
schemas/review-protocol.schema.jsonbyscripts/generate-schema-docs.mjs— do not edit this page by hand.
Frozen contract (Stage 7A, 2026-09-02; seats and claims added 2026-09-02). Immutable instructions describing a review computation, in the tfp.review/v0.1 language: a bounded, statically validated DAG with exactly two node types — review (humans or agents exercise judgment under an explicit rubric and structured output schema) and reduce (a deterministic operation from a closed catalogue over earlier structured outputs, never a model). Conditional continuation exists in one form only: a node may run when a named reduce node emitted accept, reject, or unresolved. There are no loops, expressions, user code, API calls, timers, or mutable state; a protocol describes judgment, not arbitrary work. A review stage of a VerifierContract pins one ReviewProtocol; a ReviewProcess is one execution of it; ReviewAssignments are its reviewers' work. Reviewer configuration and rubrics are written inline. Content-addressed and versioned like Contracts: a changed document is a new protocol.
Schema version: 0.3.1 · $id: https://tokensforprogress.org/schemas/review-protocol.schema.json
Fields
id
Type: string · Required: yes · Mutable: no · Pattern: ^rpt_[0-9a-z]{10,32}$
Stable opaque identifier — what Contract review stages pin and what every ReviewProcess records.
Example: "rpt_01jq6p3c0gxk"
slug
Type: string · Required: yes · Mutable: yes · Pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
URL-safe display name, unique per publisher.
Example: "extraction-2of3-escalate"
publisher_user_id
Type: string · Required: yes · Mutable: no · Pattern: ^usr_[0-9a-z]{10,32}$
Example: "usr_01jf90aa4rrc"
content_hash
Type: string · Required: yes · Mutable: no · Pattern: ^sha256:[0-9a-f]{64}$
Deterministic digest of the canonical serialization of document. Every ReviewProcess and VerificationRun review stage records it, so a process is reconstructible even after Campaign defaults change.
Example: "sha256:2b3c4d5e6f708192a3b4c5d6e7f80912a3b4c5d6e7f8091a2b3c4d5e6f7081920"
document
Type: object · Required: yes · Mutable: no
The authored tfp.review/v0.1 document: a small, statically validated, bounded DAG of review and reduce nodes. This is what is hashed and what a ReviewProcess snapshots.
document.version
Type: string · Required: yes · Mutable: —
document.purpose
Type: acceptance | triage | ranking | audit · Required: yes · Mutable: —
What the review means. acceptance: does one item meet a bar? triage: does it deserve further scarce attention? ranking: how do candidates compare? audit: how well did another evaluation process perform? Purpose is meaning, not a separate engine.
Example: "acceptance"
document.title
Type: string · Required: yes · Mutable: —
Example: "Extraction accuracy: three independent agents, expert on disagreement"
document.subject_kind
Type: submission | problem_proposal | campaign_proposal · Required: yes · Mutable: —
What one ReviewProcess of this protocol examines. v0 protocols review one subject; set-level ranking is expressed as per-candidate scored review with aggregation deferred to the consuming feature.
Example: "submission"
document.nodes
Type: array · Required: yes · Mutable: —
The DAG. Dependencies are implied by reduce inputs, review visibility.prior_nodes, and when.node; static validation requires the graph to be acyclic.
document.output
Type: string · Required: yes · Mutable: — · Pattern: ^[a-z][a-z0-9_]{0,39}$
The reduce node whose outcome is the protocol's result. Must be a reduce node, and every other node must be an ancestor of it or conditional on one.
Example: "final"
document.policy
Type: object · Required: yes · Mutable: —
Execution policy for the ReviewProcess. Finite by construction, so a process never stays open indefinitely: the node deadline bounds every seat.
created_at
Type: string · Required: yes · Mutable: no
Example: "2026-09-10T09:00:00Z"
yanked_at
Type: string \| null · Required: yes · Mutable: yes · Default: null
Write-once withdrawal from recommendation. Existing Contract pins keep resolving; existing processes keep their meaning.
Example: null
JSON Schema source
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://tokensforprogress.org/schemas/review-protocol.schema.json",
"title": "ReviewProtocol",
"description": "Frozen contract (Stage 7A, 2026-09-02; seats and claims added 2026-09-02). Immutable instructions describing a review computation, in the tfp.review/v0.1 language: a bounded, statically validated DAG with exactly two node types — `review` (humans or agents exercise judgment under an explicit rubric and structured output schema) and `reduce` (a deterministic operation from a closed catalogue over earlier structured outputs, never a model). Conditional continuation exists in one form only: a node may run when a named reduce node emitted accept, reject, or unresolved. There are no loops, expressions, user code, API calls, timers, or mutable state; a protocol describes judgment, not arbitrary work. A `review` stage of a VerifierContract pins one ReviewProtocol; a ReviewProcess is one execution of it; ReviewAssignments are its reviewers' work. Reviewer configuration and rubrics are written inline. Content-addressed and versioned like Contracts: a changed document is a new protocol.",
"x-tfp-version": "0.3.1",
"type": "object",
"additionalProperties": false,
"required": [
"id",
"slug",
"publisher_user_id",
"content_hash",
"document",
"created_at",
"yanked_at"
],
"properties": {
"id": {
"type": "string",
"pattern": "^rpt_[0-9a-z]{10,32}$",
"x-mutable": false,
"description": "Stable opaque identifier — what Contract review stages pin and what every ReviewProcess records.",
"examples": [
"rpt_01jq6p3c0gxk"
]
},
"slug": {
"type": "string",
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
"maxLength": 64,
"x-mutable": true,
"description": "URL-safe display name, unique per publisher.",
"examples": [
"extraction-2of3-escalate"
]
},
"publisher_user_id": {
"type": "string",
"pattern": "^usr_[0-9a-z]{10,32}$",
"x-mutable": false,
"examples": [
"usr_01jf90aa4rrc"
]
},
"content_hash": {
"type": "string",
"pattern": "^sha256:[0-9a-f]{64}$",
"x-mutable": false,
"description": "Deterministic digest of the canonical serialization of `document`. Every ReviewProcess and VerificationRun review stage records it, so a process is reconstructible even after Campaign defaults change.",
"examples": [
"sha256:2b3c4d5e6f708192a3b4c5d6e7f80912a3b4c5d6e7f8091a2b3c4d5e6f7081920"
]
},
"document": {
"type": "object",
"additionalProperties": false,
"required": [
"version",
"purpose",
"title",
"subject_kind",
"nodes",
"output",
"policy"
],
"description": "The authored tfp.review/v0.1 document: a small, statically validated, bounded DAG of review and reduce nodes. This is what is hashed and what a ReviewProcess snapshots.",
"properties": {
"version": {
"type": "string",
"const": "tfp.review/v0.1"
},
"purpose": {
"type": "string",
"enum": [
"acceptance",
"triage",
"ranking",
"audit"
],
"description": "What the review means. acceptance: does one item meet a bar? triage: does it deserve further scarce attention? ranking: how do candidates compare? audit: how well did another evaluation process perform? Purpose is meaning, not a separate engine.",
"examples": [
"acceptance"
]
},
"title": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"examples": [
"Extraction accuracy: three independent agents, expert on disagreement"
]
},
"subject_kind": {
"type": "string",
"enum": [
"submission",
"problem_proposal",
"campaign_proposal"
],
"description": "What one ReviewProcess of this protocol examines. v0 protocols review one subject; set-level ranking is expressed as per-candidate scored review with aggregation deferred to the consuming feature.",
"examples": [
"submission"
]
},
"nodes": {
"type": "array",
"minItems": 1,
"maxItems": 16,
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"type"
],
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]{0,39}$",
"description": "Unique within the protocol.",
"examples": [
"panel"
]
},
"type": {
"type": "string",
"enum": [
"review",
"reduce"
],
"description": "The only two node types in v0. `review` obtains judgment — from Users who claim its seats (the default) or, on an agent node, from a TfP-hosted model invocation; `reduce` combines structured outputs deterministically and never invokes a model."
},
"title": {
"type": [
"string",
"null"
],
"maxLength": 120,
"default": null
},
"when": {
"type": [
"object",
"null"
],
"additionalProperties": false,
"default": null,
"required": [
"node",
"outcome"
],
"description": "Closed-outcome conditional continuation: this node runs only when the named reduce node emitted the named outcome. The only conditional construct in v0. Null means the node always runs once its dependencies complete.",
"properties": {
"node": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]{0,39}$"
},
"outcome": {
"type": "string",
"enum": [
"accept",
"reject",
"unresolved"
]
}
}
},
"reviewers": {
"type": [
"object",
"null"
],
"additionalProperties": false,
"required": [
"kind",
"count",
"independent"
],
"description": "Who performs this node's judgment. Humans and agents are the two reviewer kinds. A human using an agent remains the accountable reviewer; the assistance is recorded as provenance on the assignment, not as a third kind.",
"properties": {
"kind": {
"type": "string",
"enum": [
"human",
"agent"
],
"examples": [
"agent"
]
},
"count": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"description": "How many distinct reviewers must complete this node.",
"examples": [
3
]
},
"independent": {
"type": "boolean",
"description": "When true, reviewers on this node cannot see one another's responses before submitting their own. Each is placed in its own independence group.",
"examples": [
true
]
},
"eligibility": {
"type": [
"object",
"null"
],
"additionalProperties": false,
"default": null,
"required": [
"rule"
],
"description": "For human reviewers: who may claim a seat on this node. TfP never assigns a human; eligible Users discover open seats and claim them. Required when kind is human; null for agents.",
"properties": {
"rule": {
"type": "string",
"enum": [
"any_participant",
"organization_members",
"campaign_reviewers",
"named_users"
],
"examples": [
"campaign_reviewers"
]
},
"organization_id": {
"type": [
"string",
"null"
],
"pattern": "^org_[0-9a-z]{10,32}$",
"default": null,
"description": "Required when rule is organization_members."
},
"user_ids": {
"type": [
"array",
"null"
],
"default": null,
"maxItems": 50,
"items": {
"type": "string",
"pattern": "^usr_[0-9a-z]{10,32}$"
},
"uniqueItems": true,
"description": "Required when rule is named_users."
}
},
"allOf": [
{
"if": {
"properties": {
"rule": {
"const": "organization_members"
}
},
"required": [
"rule"
]
},
"then": {
"properties": {
"organization_id": {
"type": "string"
}
}
}
},
{
"if": {
"properties": {
"rule": {
"const": "named_users"
}
},
"required": [
"rule"
]
},
"then": {
"properties": {
"user_ids": {
"type": "array",
"minItems": 1
}
}
}
}
]
},
"allowed": {
"type": [
"array",
"null"
],
"default": null,
"minItems": 1,
"maxItems": 20,
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"provider",
"model",
"config"
],
"properties": {
"provider": {
"type": "string",
"minLength": 1,
"maxLength": 60,
"description": "Model provider identifier.",
"examples": [
"anthropic"
]
},
"model": {
"type": "string",
"minLength": 1,
"maxLength": 120,
"description": "Exact model identifier.",
"examples": [
"claude-fable-5-1"
]
},
"config": {
"type": "object",
"description": "Provider-specific configuration TfP's hosted path executes; part of the reviewer's identity for this protocol. Provider-generic in shape, but a document is valid only if TfP's hosted path supports every key (Anthropic today: `effort` low|medium|high|xhigh|max and `max_tokens` 256..64000). Provider defaults are resolved when an assignment is issued, so the assignment records the effective configuration that ran.",
"examples": [
{
"effort": "high"
}
]
}
}
},
"description": "For TfP-hosted agent reviewers only: the exact provider/model/config combinations TfP itself will invoke to fill this node (an optional mechanism; the default is human reviewers who bring their own agents and declare them as assistance). Required when kind is agent; null for humans. There is no global model-strength hierarchy; a protocol names what it accepts."
},
"assistance": {
"type": [
"object",
"null"
],
"additionalProperties": false,
"default": null,
"required": [
"required",
"allowed"
],
"description": "Human nodes only. Whether reviewers must declare an agent that assisted them, and which are allowed. TfP enforces the declaration on submission and records it; it cannot observe what was actually used.",
"properties": {
"required": {
"type": "boolean"
},
"allowed": {
"type": "array",
"maxItems": 20,
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"provider",
"model"
],
"properties": {
"provider": {
"type": "string",
"minLength": 1,
"maxLength": 60
},
"model": {
"type": "string",
"minLength": 1,
"maxLength": 120
}
}
},
"description": "Provider/model pairs in the author's own words — the reviewer runs these, TfP does not, so no entry need be one TfP can invoke. Empty means any declared assistance is acceptable."
}
}
}
},
"allOf": [
{
"if": {
"properties": {
"kind": {
"const": "human"
}
}
},
"then": {
"properties": {
"eligibility": {
"type": "object"
},
"allowed": {
"type": "null"
}
},
"required": [
"eligibility"
]
}
},
{
"if": {
"properties": {
"kind": {
"const": "agent"
}
}
},
"then": {
"properties": {
"allowed": {
"type": "array"
},
"eligibility": {
"type": "null"
}
},
"required": [
"allowed"
]
}
}
],
"default": null
},
"visibility": {
"type": [
"object",
"null"
],
"additionalProperties": false,
"required": [
"prior_nodes",
"stage_evidence",
"hidden"
],
"description": "What a reviewer on this node may inspect. The subject itself, and its Submission and Artifacts where applicable, are always visible; everything else is opt-in.",
"properties": {
"prior_nodes": {
"type": "array",
"maxItems": 20,
"items": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]{0,39}$"
},
"uniqueItems": true,
"description": "Ids of earlier nodes whose outputs this node's reviewers may read — how a meta-reviewer sees the panel's reports. Must be dependencies of this node.",
"examples": [
[]
]
},
"stage_evidence": {
"type": "boolean",
"description": "Whether read-only evidence from earlier VerifierContract stages (a sandbox stage's evidence summary, for example) is shown.",
"examples": [
true
]
},
"hidden": {
"type": "array",
"maxItems": 5,
"uniqueItems": true,
"items": {
"type": "string",
"enum": [
"submitter_identity",
"submitter_organization",
"prior_scores",
"attempt_notes"
]
},
"description": "Blinding: information withheld from reviewers on this node.",
"examples": [
[
"submitter_identity"
]
]
}
},
"default": null
},
"conflicts": {
"type": [
"object",
"null"
],
"additionalProperties": false,
"required": [
"exclude_submitter",
"exclude_submitter_organization"
],
"description": "Reviewers excluded because of their relationship to the subject. Applies to human reviewers only. Null on a human node means the defaults: exclude_submitter true, exclude_submitter_organization false. Null (or anything) on an agent node has no effect.",
"properties": {
"exclude_submitter": {
"type": "boolean",
"examples": [
true
]
},
"exclude_submitter_organization": {
"type": "boolean",
"description": "Exclude current members of the Organization the Attempt is attributed to.",
"examples": [
false
]
}
},
"default": null
},
"skills": {
"type": [
"array",
"null"
],
"default": null,
"maxItems": 10,
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"skill_version_id",
"requirement"
],
"properties": {
"skill_version_id": {
"type": "string",
"pattern": "^skv_[0-9a-z]{10,32}$"
},
"requirement": {
"type": "string",
"enum": [
"required",
"recommended"
]
}
}
},
"description": "SkillVersions supplied to reviewers on this node as context — a technoeconomic-analysis review can require the exact TEA methodology Skill. Provided automatically to agents and surfaced to humans; the assignment records what was supplied."
},
"rubric": {
"type": [
"object",
"null"
],
"additionalProperties": false,
"required": [
"instructions_md",
"response_schema",
"rationale",
"citations",
"confidence"
],
"description": "What reviewers are asked and the exact shape of what they must return. Inline with the protocol; there is no separate Rubric object. Four things come back from every review: the domain-specific `response` (validated against response_schema), and the universal rationale_md, citations, and confidence, whose requirements are declared here so analysis across protocols reads the same fields.",
"properties": {
"instructions_md": {
"type": "string",
"minLength": 1,
"maxLength": 20000,
"description": "Human-readable instructions given to every reviewer on this node.",
"examples": [
"Assess whether the extracted reaction record faithfully represents the cited source. Cite the passage supporting each judgment."
]
},
"response_schema": {
"type": "object",
"description": "JSON Schema (draft 2020-12) every response must satisfy: the domain-specific structured judgment. Reducers reference fields declared here. Do not declare rationale, citations, or confidence here; those are universal fields with their own requirements below.",
"examples": [
{
"type": "object",
"required": [
"verdict"
],
"properties": {
"verdict": {
"enum": [
"pass",
"fail",
"uncertain"
]
}
}
}
]
},
"rationale": {
"type": "string",
"enum": [
"required",
"optional"
],
"description": "Whether every completed assignment must carry a non-empty rationale_md.",
"examples": [
"required"
]
},
"citations": {
"type": "object",
"additionalProperties": false,
"required": [
"min_items"
],
"description": "Minimum number of structured citations each completed assignment must carry; 0 makes them optional.",
"properties": {
"min_items": {
"type": "integer",
"minimum": 0,
"maximum": 50,
"examples": [
1
]
}
}
},
"confidence": {
"type": "string",
"enum": [
"required",
"optional"
],
"description": "Whether every completed assignment must declare a confidence in [0, 1] on the assignment's universal confidence field.",
"examples": [
"required"
]
}
},
"default": null
},
"inputs": {
"type": [
"array",
"null"
],
"default": null,
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]{0,39}$"
},
"uniqueItems": true,
"description": "reduce: the upstream nodes read, in order. k_of_n and threshold take exactly one review node; coalesce takes one or more reduce nodes in priority order."
},
"op": {
"type": [
"string",
"null"
],
"enum": [
"k_of_n",
"threshold",
"coalesce",
null
],
"default": null,
"description": "reduce: the deterministic operation. The v0 catalogue is closed: k_of_n (counting a predicate over responses, with separate accept and reject thresholds), threshold (an aggregate of a numeric field against accept/reject cut-offs), coalesce (the first resolved outcome among earlier reducers)."
},
"config": {
"type": [
"object",
"null"
],
"additionalProperties": false,
"description": "Operation configuration. k_of_n: field, accept, reject (eq/in predicates with disjoint values). threshold: field, aggregate, accept_gte, reject_lt. coalesce: none. See the reducer catalogue in docs/reference/reviews.md.",
"properties": {
"field": {
"type": "string",
"minLength": 1,
"maxLength": 120,
"description": "Field of each upstream response the reducer reads.",
"examples": [
"verdict"
]
},
"accept": {
"type": "object",
"additionalProperties": false,
"required": [
"predicate",
"k"
],
"properties": {
"predicate": {
"type": "object",
"additionalProperties": false,
"required": [
"op",
"value"
],
"properties": {
"op": {
"type": "string",
"enum": [
"eq",
"in"
],
"description": "k_of_n compares by membership only, so accept and reject match sets can be proven disjoint statically. Numeric comparison belongs to the threshold reducer."
},
"value": {
"description": "A scalar for eq, an array of scalars for in."
}
}
},
"k": {
"type": "integer",
"minimum": 1,
"description": "How many responses must match."
}
},
"description": "k_of_n: accept when at least k responses satisfy the predicate."
},
"reject": {
"type": "object",
"additionalProperties": false,
"required": [
"predicate",
"k"
],
"properties": {
"predicate": {
"type": "object",
"additionalProperties": false,
"required": [
"op",
"value"
],
"properties": {
"op": {
"type": "string",
"enum": [
"eq",
"in"
],
"description": "k_of_n compares by membership only, so accept and reject match sets can be proven disjoint statically. Numeric comparison belongs to the threshold reducer."
},
"value": {
"description": "A scalar for eq, an array of scalars for in."
}
}
},
"k": {
"type": "integer",
"minimum": 1,
"description": "How many responses must match."
}
},
"description": "k_of_n: reject when at least k responses satisfy the predicate. Static validation requires the accept and reject value sets to be disjoint and accept.k + reject.k > reviewer count, so both outcomes can never hold; when neither holds the outcome is unresolved."
},
"aggregate": {
"type": "string",
"enum": [
"mean",
"median",
"min",
"max"
],
"description": "threshold: how numeric responses are combined."
},
"accept_gte": {
"type": "number",
"description": "threshold: accept when the aggregate is at least this."
},
"reject_lt": {
"type": "number",
"description": "threshold: reject when the aggregate is below this. Must not exceed accept_gte; between them the outcome is unresolved."
}
},
"default": null
}
},
"allOf": [
{
"if": {
"properties": {
"type": {
"const": "review"
}
}
},
"then": {
"required": [
"reviewers",
"visibility",
"rubric"
],
"properties": {
"reviewers": {
"type": "object"
},
"visibility": {
"type": "object"
},
"rubric": {
"type": "object"
},
"inputs": {
"type": "null"
},
"op": {
"type": "null"
},
"config": {
"type": "null"
}
}
}
},
{
"if": {
"properties": {
"type": {
"const": "reduce"
}
}
},
"then": {
"required": [
"inputs",
"op",
"config"
],
"properties": {
"inputs": {
"type": "array"
},
"op": {
"type": "string"
},
"config": {
"type": "object"
},
"reviewers": {
"type": "null"
},
"visibility": {
"type": "null"
},
"conflicts": {
"type": "null"
},
"rubric": {
"type": "null"
},
"skills": {
"type": "null"
}
}
}
}
]
},
"description": "The DAG. Dependencies are implied by reduce inputs, review visibility.prior_nodes, and when.node; static validation requires the graph to be acyclic."
},
"output": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]{0,39}$",
"description": "The reduce node whose outcome is the protocol's result. Must be a reduce node, and every other node must be an ancestor of it or conditional on one.",
"examples": [
"final"
]
},
"policy": {
"type": "object",
"additionalProperties": false,
"required": [
"assignment_timeout_hours"
],
"description": "Execution policy for the ReviewProcess. Finite by construction, so a process never stays open indefinitely: the node deadline bounds every seat.",
"properties": {
"assignment_timeout_hours": {
"type": "integer",
"minimum": 1,
"maximum": 720,
"description": "The node deadline: how long, from the moment a review node opens its seats, eligible reviewers have to claim and complete them. A seat still open at the deadline ends the process inconclusive (reviewers_unavailable).",
"examples": [
72
]
},
"max_replacements_per_node": {
"type": "integer",
"minimum": 0,
"maximum": 10,
"description": "DEPRECATED, ignored. Retired with the pull-based change (2026-09-02): accepted only so earlier documents still validate; omit it from new documents. Seats reopen on their own; the node deadline is the bound.",
"examples": [
2
]
},
"claim_hold_minutes": {
"type": "integer",
"minimum": 15,
"maximum": 1440,
"default": 120,
"description": "How long a claim holds a seat before it lapses and the seat reopens. Short by design, so nobody can sit on a seat; must be shorter than assignment_timeout_hours.",
"examples": [
120
]
}
}
}
},
"x-mutable": false
},
"created_at": {
"type": "string",
"format": "date-time",
"x-mutable": false,
"examples": [
"2026-09-10T09:00:00Z"
]
},
"yanked_at": {
"type": [
"string",
"null"
],
"format": "date-time",
"x-mutable": true,
"default": null,
"description": "Write-once withdrawal from recommendation. Existing Contract pins keep resolving; existing processes keep their meaning.",
"examples": [
null
]
}
}
}