Proof-Carrying Numbers
Proof-Carrying Numbers (PCN) is a presentation-layer protocol for numeric fidelity in text generated by language models. A model emits each number as a claim tag that names the source record the number rests on. The application that renders the text looks the record up, compares the number with it under a declared policy, and draws a verification mark. The model cannot draw the mark, so a mark means the comparison was made and passed.
The protocol, its specification, shared test fixtures, and the packages that implement it are in the worldbank/pcn repository. The formal description and the evaluation are in the paper Proof-Carrying Numbers (arXiv:2509.06902). The status of the packages is on the tools catalogue.
What a verified mark means
A verified mark means one thing: the number in the text matches the value of the record it cites, under the policy stated in the tag. It says nothing about whether the record is the right one for the question, whether the statistical source and its method are sound, or whether the sentence around the number interprets it correctly. A model can cite a correct value for the wrong year or the wrong country, and the mark will be drawn if the value matches.
PCN therefore removes one failure, a number that differs from its source, and leaves the others to the usual means: provenance in the record, a review of the answer, and the evaluation of the system that produced it. The absence of a mark communicates uncertainty; it does not say the number is wrong.
How it works
- Claims. The application ingests the results of the tools the model called, for example the response of an MCP data tool, and stores each value as a claim with an identifier, a value, and context such as the area and the period.
- Claim tags. The model is instructed to write every number it takes from a tool result as a tag:
<claim id="clm-7ef6" policy="rounded" decimals="1">5.7</claim>. The identifier is the claim's; the policy says how strictly the comparison is made. - Verification. The renderer parses the tags, finds the claim by identifier, and applies the policy. A tag whose identifier is unknown, or whose number fails the policy, is unverified.
- Rendering. The renderer draws the mark for verified numbers and a different treatment for unverified ones, with the claim's details available on inspection. Verification happens in the renderer, so a model that writes a mark into its text produces nothing the renderer recognizes.
The protocol fails closed: a number without a tag, or with a tag the renderer cannot resolve, is never marked verified.
Policies
A policy names the relation under which a number and a claim value match.
| Policy | Match when | Typical use |
|---|---|---|
exact | The normalized strings are equal | Counts, identifiers, exact figures |
rounded | Both values round to the same number at decimals places | Figures shown with fewer decimals than the record |
tolerance | The relative difference is within tolerance | Converted or re-expressed values |
percent | Equal after reading percent signs | Shares and rates |
abbr | Equal after expanding k, m, and b suffixes | Large counts and currency amounts |
ratio | Equal after reading "a in b" or "a/b" | Ratios written in words |
range | The claim value lies within the stated range | A number given as a range |
year | The four-digit years are equal | Periods |
auto | Any of the above in a fixed order, with rounding to a whole number and a two percent tolerance | Prose where the application has not chosen a policy |
The policy reference in the repository gives the definitions, and the specification folder holds the fixtures that every implementation must pass.
Packages
| Package | Language | Purpose |
|---|---|---|
@pcn-js/core | TypeScript | Claims manager, extractors for tool results, parsing of claim tags, comparison by policy, processing of HTML and markdown |
@pcn-js/ui | TypeScript, React 18 or later | The claim mark component, a provider that holds the claims, and default styles |
@pcn-js/data360 | TypeScript | A preset for the Data360 MCP server's data tool, whose results carry claim_id, OBS_VALUE, REF_AREA, and TIME_PERIOD |
pcn-core | Python 3.10 or later | The comparison engine: a claim type, the policy types, and compare_by_policy |
The TypeScript packages are published on npm under the @pcn-js scope and are installed with npm install @pcn-js/core @pcn-js/ui @pcn-js/data360. The Python core is installed from the repository until it is published to PyPI. The integration guide describes the two integration paths: the Data360 preset for applications that use the Data360 MCP server, and a custom extractor for any other tool result shape. The repository's example chat application shows the preset in use.
Where it is used
The Data AI Chatbot, which the World Bank runs as Data360 Chat, renders every number in an answer through the PCN packages, with the Data360 MCP server as the source of claims. The end-to-end reference implementation on this site uses the Python core to verify an answer composed from the results of an MCP server over the cookbook's example catalog, and shows a planted wrong number being flagged and an untagged number receiving no mark.
What an adopting application must provide
- A tool layer whose results carry a stable identifier per value, so that a claim can be named. An MCP server over the organization's data is the usual source.
- Ingestion of those results into the claims manager, either through the provided components or by calling the manager when results arrive.
- A system prompt that instructs the model to write claim tags, and a renderer that uses the claim mark component where numbers appear.
- A choice of policy per context:
roundedwith the number of decimals the organization publishes is the common choice for official statistics.
Limitations
- A verified mark is a statement about one comparison. It does not establish that the source, the method, or the interpretation is correct.
- PCN verifies numbers the model tagged. Extraction of untagged numbers from free text is outside the protocol; such numbers stay unmarked.
- The comparison engine handles the policies above. A value expressed in a different unit from the record fails unless the application converts it before comparison.
- The TypeScript packages assume a React tree for the user interface; the core can be used without React for HTML processing.
- The Python core covers comparison only. Parsing of claim tags and rendering are implemented in the TypeScript packages, and an application in Python writes its own parser, as the reference implementation does.
Resources
- Repository: packages, specification, fixtures, and the example application.
- Paper: the protocol and its properties.
- Trust chapter of the dissemination cookbook: the simplest form of the check, as a script over a values file.
- Agents cookbook, provenance chapter: claim-ready responses from an MCP server.