# Read OCP

> What a consumer must accept, what it should weigh, and a small reader.

A consumer reads OCP documents to compare, price, learn from or show runs. The format is designed so that a careful reader handles every conforming document, from any producer and any version.

## What a reader must handle

- **Every version.** Check `ocp`, and read each document by the rules of the version it declares: 0.1 and 0.2 documents have fewer fields, and 0.1 edges may lack a tier. Or upgrade them first with [`loopmath ocp migrate`](https://orchestrationcontextprotocol.io/docs/develop/validate#migrate).
- **Unknown fields.** Ignore fields you do not know, at every level, and every `ext` namespace you do not understand. Never fail on them.
- **Open vocabularies.** Values outside a recommended list, such as a new harness name or effort level, are valid, and a reader accepts them. Piece roles are never rewritten; they are compared as written.
- **Missing values.** An absent cost means unknown, not zero. An absent `outcome.evidence` means `asserted`. A missing capability means unknown.

## What a reader should weigh

- **Evidence tiers.** Weigh outcomes and signals by tier. See [Evidence](https://orchestrationcontextprotocol.io/docs/learn/evidence).
- **Cost basis.** `allocated` counts were apportioned from a coarser total; weigh them less than `measured` ones.
- **Dollars.** Prices change and tools price differently. Prefer recomputing dollars from the token streams and a dated price table over trusting `usd`, and use `cost.tariff` to see how a stored `usd` was priced.
- **Model names.** Use `model.id` when present. `model.raw` is the tool's own label, kept as ground truth; canonicalizing it is the reader's job.
- **Duplicates across files.** Preferences are copied into every member of a slate; deduplicate them by `id`.
- **Configuration ids.** Two runs with the same `configuration.id` declared the same workflow and baseline settings. An attempt may still override its piece's setting, so check attempt `setting` fields before pooling runs by id. A careful reader recomputes the id ([E190](https://orchestrationcontextprotocol.io/specification/0.3/conformance#e190)) before trusting it.

## A small reader

Observed tokens per model and outcomes per harness, over any number of documents. Standard library only. An attempt without a cost record is counted as unknown, never as zero.

```python read.py
import json
import sys
from collections import defaultdict

STREAMS = ("input_tokens", "cached_input_tokens", "output_tokens")
TERMINAL = {"done", "failed", "rejected", "canceled", "settled_unverified", "lost"}

observed = defaultdict(int)  # tokens seen, per model
unknown = defaultdict(int)   # attempts without a cost record, per model
outcomes = defaultdict(lambda: defaultdict(int))

def cache_writes(cost):
    # the total when given, else the retention buckets, never both
    if cost.get("cache_creation_tokens") is not None:
        return cost["cache_creation_tokens"]
    return (cost.get("cache_creation_5m_tokens") or 0) + (cost.get("cache_creation_1h_tokens") or 0)

for path in sys.argv[1:]:
    with open(path, encoding="utf-8") as f:
        doc = json.load(f)
    if doc.get("ocp") not in ("0.1", "0.2", "0.3"):
        print(f"skip {path}: unknown version {doc.get('ocp')!r}")
        continue
    for a in doc.get("attempts") or []:
        model = a.get("model") or {}
        name = model.get("id") or model.get("raw") or "unknown"  # canonical id when known, else the raw label
        cost = a.get("cost")
        if isinstance(cost, dict):
            # reasoning_tokens are left out: output_tokens already includes them
            observed[name] += sum(cost.get(k) or 0 for k in STREAMS) + cache_writes(cost)
        else:
            unknown[name] += 1  # an absent cost is unknown, not zero
        if a.get("status") in TERMINAL:
            outcome = a.get("outcome") or {}
            tier = outcome.get("evidence") or "asserted"  # missing evidence is the weakest tier
            outcomes[a.get("harness") or "unknown"][(a["status"], tier)] += 1

for name in sorted(set(observed) | set(unknown), key=lambda m: -observed.get(m, 0)):
    print(f"{name:24} {observed.get(name, 0):>12,} tokens observed, {unknown.get(name, 0)} attempts without cost")
for harness, counts in outcomes.items():
    print(harness, dict(counts))
```

```bash
python3 read.py runs/*.ocp.json
```
