Pinakes Verifiable Retrieval
Pinakes is a deterministic, verifiable input layer for AI agents over 26
public biomedical sources, shipped as one static Go binary that exposes the same
8 verbs over an MCP server (pinakes_<verb> tools), an identical CLI
(pinakes <verb>), and a loopback REST server. Its value is invisible from the
tool schemas, which is why this skill exists: every result pins a
content-addressed snapshot (byte-reproducible forever), retrieval is
complete-or-fail (reconciled against the source's authoritative total — never
a silent partial), and every result ships a re-runnable manifest that
verify re-derives offline. This skill teaches the judgement layer — pin or
you get no determinism, read completeness honestly, verify before you cite — not
the schemas (tools/list and pinakes catalog already carry field names,
operators, and enums; do not duplicate them here).
Boundary. Pinakes fixes the input only. It does not run analysis, does
not build evolutionary trees, and never claims a source is wrong — the public
databases are authoritative. It fixes naive client retrieval (no imposed order,
no reconciliation, no provenance). It covers public sources only. The arXiv
literature corpus is not yet shipped — do not assume it. Hand the verified
manifest to a domain skill for the actual science.
When to Use This Skill
✅ Use when:
- An agent must pull biomedical data that has to be reproducible /
deterministic / byte-identical across runs.
- You need a record count that goes in a methods section and must reproduce
next month.
- You need a cited fact a reviewer can independently re-fetch and check.
- You are handed a manifest and must confirm it reproduces before building on
it (verify-before-cite).
- A query was rejected (
UNKNOWN_FIELD, BAD_ENUM, INVALID_FILTER,
UNSATISFIABLE_QUERY) and the agent must self-correct rather than retry blind.
- You must know whether a result is complete vs best_effort vs partial, or
whether it is reproducible at all.
- You are pulling from PDB, UniProt, NCBI Protein, NCBI Virus, ClinVar, Ensembl,
gnomAD, PubChem, AlphaFold, ChEMBL, ClinicalTrials, openFDA, cBioPortal, or
NCBI Datasets and want provenance, not just bytes.
❌ Do not use when:
- The task is analysis (differential expression, alignment, tree-building,
modeling). Use this to fetch the verified input, then a domain skill.
- You want to assert a source is wrong or compute a "true eternal count" —
Pinakes does neither; a count is a property of a pin, not of all time.
- The source is private/internal or behind a login — Pinakes serves public
databases only.
- You need the arXiv corpus — not shipped yet.
- A raw, throwaway lookup where reproducibility genuinely does not matter (rare —
if it will be cited or re-run, it matters).
Clarification Questions
1. ⚠️ CRITICAL: ASK THIS FIRST — Will this result be reported, cited, or re-run?
If yes, you MUST pin a concrete snapshot_version (sha256:…, never empty,
never latest) and verify the manifest before citing. An unpinned result is
served from latest ⇒ reproducible:false, and a re-run next month silently
returns a different set — so a bare count or fact with no pin is not a fact you can
cite. If the answer is genuinely no (a throwaway lookup nobody will repeat),
you may skip the pin — but confirm that explicitly, because this is rare.
2. Which source, and is it materializable?
- Which of the 26 sources answers the question (run
catalog to confirm the id
and its filter schema)?
- Is that source L2+ and HostServe? A copyleft source (ChEMBL, served
live) can never be
reproducible:true — if the task demands reproducibility,
route to a materializable source instead (see Picking a Connector).
3. Does the answer have to be complete, or is best-effort acceptable?
- If it must be the whole answer, do not pass a
limit — a cap below the
authoritative total downgrades the result to best_effort (it cannot be
reconciled). Let complete-or-fail reconcile.
- If a sample is fine, label it
best_effort honestly — never present a capped or
best-effort set as "all matching records".
Setup & Surface Detection
Pinakes is client-neutral: the same 8 verbs are an MCP tool and a CLI
subcommand. Detect the surface and degrade gracefully.
Rule: use the MCP pinakes_<verb> tool if it is present in your tools; else
shell out to the identical pinakes <verb> CLI. Every recipe below is shown in
both forms — they are interchangeable.
Confirm wiring with pinakes catalog (CLI) or pinakes_catalog (MCP) before the
first real query. Depth: references/setup.md.
The Core Loop (1–8)
Run this loop for any retrieval that will be reported, cited, or re-run. The
hinge steps are 4 (pin a snapshot), 5 (read the manifest), and 7
(verify) — skip any one and the determinism guarantee silently evaporates.
- Catalog.
pinakes_catalog / pinakes catalog to list sources (id,
maturity, license); scope to one source to get its filter schema (fields,
operators, enums). This is how you learn what is queryable.
- Validate locally. Check every filter field/operator/value against that
schema before any network call. A bad one is rejected locally with a precise
code (
UNKNOWN_FIELD / BAD_ENUM / INVALID_FILTER) — fix it here, cheaply.
- Estimate (optional).
pinakes_estimate / pinakes estimate for a dry-run
record/byte/call count. An estimate never substitutes for reconciliation —
it is a cost preview, not a completeness claim.
- Retrieve WITH a pinned
snapshot_version. search / get / resolve,
always passing a concrete snapshot_version (sha256:…). Empty or
latest ⇒ not reproducible. This is the #1 footgun (see below).
- Read Completeness +
reproducible. Inspect the manifest: is
completeness.state complete? Is reproducible true? Do not report until
you have read these — the records alone don't tell you.
- Export (optional).
pinakes_export / pinakes export to materialize to
parquet/json/fasta/vcf/mmcif. The reproducibility anchor stays the canonical
NDJSON logical_record_hash, not the export bytes.
- Verify.
pinakes_verify / pinakes verify --manifest @manifest.json
re-derives the count and record hash from the pinned snapshot offline.
Exit 0 / verified:true ⇒ it reproduces. A refusal ⇒ stop, do not cite.
- Cite the manifest. The citation is the manifest:
source_id, pinned
snapshot_version, logical_record_hash, completeness.state, and per-source
license/attribution. Hand this to the next skill.
Copy-able checklist:
[ ] 1. catalog → confirmed source + read filter schema
[ ] 2. filters validated locally (no UNKNOWN_FIELD / BAD_ENUM / INVALID_FILTER)
[ ] 3. (estimate, if cost matters — not a completeness claim)
[ ] 4. snapshot_version pinned (concrete sha256:… — NOT empty, NOT "latest")
[ ] 5. manifest read: completeness.state == complete? reproducible == true?
[ ] 6. (export, if a file is needed — hash is over NDJSON, not export bytes)
[ ] 7. verify → exit 0 / verified:true (offline)
[ ] 8. cited the manifest (source_id + snapshot_version + logical_record_hash
+ completeness + license/attribution)
Pin or no determinism (the #1 footgun)
This is the single most common failure. The snapshot_version is what makes a
result reproducible. No pin ⇒ the engine serves latest ⇒
reproducible:false, and a re-run next month silently returns a different set.
A bare count or fact with no pin is not a fact you can cite.
A manifest is reproducible:true only when ALL of these hold (the engine's
SetReproducible enforces every one — you cannot bypass it):
| # |
Condition |
Source of truth |
| 1 |
Served from a pinned snapshot (served_from = snapshot@…) |
ServedFrom.DeterminismClaimable() |
| 2 |
Completeness is complete and reconciled (count == authoritative) |
Completeness.Reconciled() |
| 3 |
At least one source, and every source is L2+ (any L0/L1 ⇒ false) |
MaturityLevel.DeterminismClaimable() |
| 4 |
Every source pins a concrete snapshot — not empty, not latest |
SetReproducible |
If any row fails, reproducible is false — honestly. The fix is almost always
row 1/4: pass a snapshot_version. Full table:
references/manifest-and-completeness.md.
Reading the Manifest
The manifest is the product. Read it; never infer from the record list.
Completeness decision table (completeness.state):
| State |
Means |
What you may say |
Action |
complete |
Retrieved count reconciled against the source's authoritative total and they match |
"the whole answer (N/N)" |
Safe to cite as complete |
best_effort |
No trustworthy authoritative total exists (carries a mandatory reason) |
"a best-effort set, completeness unproven" — never "all" |
Report the reason; do not claim complete |
partial |
A multi-source query where ≥1 upstream failed (has reason + resume_cursor) |
"partial; some upstream failed" |
Resume via cursor or report the gap |
The limit footgun. A limit below the source's authoritative total cannot
be reconciled, so the engine downgrades the result to best_effort — not
complete. If you need a complete answer, drop the cap and let
complete-or-fail reconcile, or report honestly that the set is best_effort.
Treating a limit-capped set as "all matching records" is a classic error.
Key manifest fields to cite: manifest_schema_version, engine_version, per-
source snapshot_version + maturity + license, logical_record_hash (the
SHA-256 over canonical NDJSON — the anchor), completeness, served_from, and
reproducible. Depth:
references/manifest-and-completeness.md.
Picking a Connector
Short routing table (26 sources). L0 = catalog-only (not queryable); L1 =
queryable but never reproducible (live proxy); L2+ = reproducible against
a pinned snapshot. All 26 shipped connectors are L2. catalog is the live
source of truth; this table is the at-a-glance version, the full one with SPDX and
attribution flags lives in references/sources.md.
| Source id |
Domain |
Maturity |
License posture |
pdb |
structures |
L2 |
public-domain → materializable |
uniprot |
proteins |
L2 |
attribution (CC-BY-4.0) → materializable |
ncbi-protein |
sequences |
L2 |
public-domain → materializable |
ncbi-virus |
viruses |
L2 |
public-domain → materializable |
ncbi-datasets |
genes |
L2 |
public-domain → materializable |
clinvar |
variants |
L2 |
public-domain → materializable |
ensembl |
genomics |
L2 |
no-restrictions → materializable |
gnomad |
variants |
L2 |
public-domain → materializable |
pubchem |
compounds |
L2 |
public-domain → materializable |
alphafold |
structures |
L2 |
attribution (CC-BY-4.0) → materializable |
clinicaltrials |
clinical_trials |
L2 |
public-domain → materializable |
openfda |
drug_labels |
L2 |
public-domain → materializable |
opentargets |
target-disease |
L2 |
public-domain → materializable |
reactome |
pathways |
L2 |
public-domain → materializable |
interpro |
protein_families |
L2 |
public-domain → materializable |
complex-portal |
protein_complexes |
L2 |
public-domain → materializable |
rhea |
reactions |
L2 |
attribution (CC-BY-4.0) → materializable |
gene-ontology |
ontology |
L2 |
attribution (CC-BY-4.0) → materializable |
gtex |
expression |
L2 |
IndexProxy (redistribute:false) — see note |
gwas-catalog |
gwas |
L2 |
IndexProxy (redistribute:false) — see note |
jaspar |
tf_motifs |
L2 |
IndexProxy (redistribute:false) — see note |
monarch |
phenotypes |
L2 |
IndexProxy (redistribute:false) — see note |
hpo |
phenotypes |
L2 |
IndexProxy (redistribute:false) — see note |
cbioportal |
cancer_genomics |
L2 |
IndexProxy (redistribute:false) — see note |
chembl |
drugs |
L2 |
copyleft (share-alike) → served LIVE, reproducible:false |
guide-to-pharmacology |
pharmacology |
L2 |
copyleft (share-alike) → served LIVE, reproducible:false |
⚠️ Copyleft caveat (ChEMBL, Guide to PHARMACOLOGY). These two are
Creative Commons Attribution-ShareAlike (copyleft / share-alike). The engine
refuses to materialize a share-alike source, so every result from them is
served live and is reproducible:false by construction — no pin makes it
reproducible. Carry the attribution string; for a reproducible drug-data answer,
prefer a materializable source. Full routing, license postures, and attribution
rules: references/sources.md.
Worked Recipe A — Ebola, reproduced (flagship)
Goal: the complete-genome records for Zaire ebolavirus in NCBI Virus,
pinned, complete, and offline-verified. Exact values from the repo case study.
MCP form:
{
"name": "pinakes_search",
"arguments": {
"source_id": "ncbi-virus",
"filters": [
{ "field": "organism_taxon_id", "operator": "eq", "value": "186538" },
{ "field": "complete_only", "operator": "eq", "value": "true" },
{ "field": "released_since", "operator": "gte", "value": "2023-01-01" }
],
"snapshot_version": "sha256:b1983c09cd5c70137db4724494d2efaf263baa88c795b4ff1a457ba82fe72f90"
}
}
Then pinakes_verify with the returned manifest.
CLI form (identical):
PIN=sha256:b1983c09cd5c70137db4724494d2efaf263baa88c795b4ff1a457ba82fe72f90
pinakes search --source-id ncbi-virus \
--filters organism_taxon_id:eq:186538 \
--filters complete_only:eq:true \
--filters released_since:gte:2023-01-01 \
--snapshot-version "$PIN" # → 32 records, Complete (32/32)
pinakes verify --manifest @manifest.json # → {"verified": true}, offline
Result (this pin): 32 records, completeness.state = complete (32 == 32
authoritative), logical_record_hash = sha256:01fffd0c…, NCBI datasets version
18.30.1, reproducible: true. verify re-derives the count and hash from the
snapshot offline — a shortened or altered snapshot is refused
(COMPLETENESS_DRIFT / HASH_MISMATCH).
Cite it like this: "NCBI Virus, complete-genome records for taxon 186538
released since 2023-01-01; snapshot sha256:b1983c09…; 32 records, Complete
(32/32); logical_record_hash sha256:01fffd0c…; verified offline." The "32" is a
property of this pin, not an eternal count — NCBI grows daily; the pin always
yields this set. More recipes (UniProt, resolve, export, multi-filter):
references/recipes.md.
Error Handling
Stable, surface-independent codes (identical across MCP/CLI/REST). Group them by
action, not by name:
| Band |
Codes |
Action |
| Self-correct locally |
UNKNOWN_FIELD, BAD_ENUM, INVALID_FILTER |
Re-read the catalog filter schema; fix the field / value / operator before any network call |
| Self-correct the query |
UNSATISFIABLE_QUERY |
Caller-fixable (maps to 4xx/422); read the message, change the query (add a co-filter, shrink the window) — never retry unchanged |
| Pin / input problems |
UNKNOWN_SOURCE, SNAPSHOT_NOT_FOUND, UNPINNED_SOURCE, IMMATURE_SOURCE |
Fix the source id or supply a valid pinned snapshot_version; an L0/L1 source cannot be reproducible |
| Verify refusals (manifest input) |
MANIFEST_NIL, NOT_REPRODUCIBLE, NO_SOURCES, MULTI_SOURCE_UNSUPPORTED |
The manifest can't be verified as posed; report it — do not fabricate a pass |
| Drift refusals — a FEATURE |
HASH_MISMATCH, COMPLETENESS_DRIFT, SPEC_DRIFT, FILTER_DRIFT, SERIALIZER_DRIFT, SCHEMA_DRIFT, GRAPH_DRIFT, PROVENANCE_DRIFT, SERVED_FROM_DRIFT, LICENSE_DRIFT, ENGINE_MISMATCH, MANIFEST_SCHEMA_DRIFT, QUERY_SNAPSHOT_MISMATCH, MANIFEST_DRIFT |
Stop and report the exact code. A drift refusal caught a real reproducibility problem; it is not a bug to retry around |
| Fallback |
INTERNAL |
Unexpected; report verbatim, do not paper over |
A verify that returns a drift code is doing its job — refusing a result that
does not reproduce. Full ~28-code table grouped by recover-or-stop:
references/errors.md.
Decision Points
| Decision |
Default |
Override when |
Pin a snapshot_version? |
Always |
Never skip if the result is reported/cited/re-run |
| MCP tool vs CLI |
MCP pinakes_<verb> if present |
CLI pinakes <verb> when no MCP tool; same args |
Apply a limit? |
No (limit ⇒ best_effort) |
Only for a deliberate sample you label non-complete |
estimate before search? |
Skip for small queries |
Use when cost/size is unknown — not a completeness claim |
| Verify before citing? |
Always |
No exception — verify is the gate |
| Use ChEMBL for a reproducible answer |
No (served live) |
Use only when live drug data is acceptable; carry attribution |
Accept best_effort |
Only if labeled as such |
Never present best_effort/partial as "all" |
Common Issues
| Issue |
Cause |
Solution |
reproducible:false on a clean query |
No snapshot_version (served latest) |
Pass a concrete sha256:… pin |
| Count differs on re-run |
Ran against latest; source grew |
Pin a snapshot; report count as a property of the pin |
Result marked best_effort unexpectedly |
A limit below the authoritative total |
Drop the limit; let complete-or-fail reconcile |
IMMATURE_SOURCE on verify |
Source is L0/L1 (not L2+) |
Reproducibility is impossible below L2; pick an L2+ source |
| ChEMBL never reproducible |
Copyleft (share-alike) → served live |
Expected; carry attribution, prefer a materializable source for reproducibility |
UNSATISFIABLE_QUERY loops |
Retried identical filters |
Read the message; change the query (co-filter, smaller window) |
UNKNOWN_FIELD / BAD_ENUM |
Filter not in the source schema |
Re-read catalog --source-id <id>; fix locally before the call |
SNAPSHOT_NOT_FOUND |
Pinned a snapshot not in the store |
Use a snapshot the catalog/case study actually pins |
HASH_MISMATCH / COMPLETENESS_DRIFT on verify |
Snapshot shortened/altered, or drift |
Stop; do not cite — the refusal is correct |
MCP client can't find pinakes |
Not on the GUI app's PATH |
Use the absolute path from which pinakes in the config |
| Export bytes differ but hash matches |
Export format isn't the anchor |
The anchor is the NDJSON logical_record_hash, not export bytes |
| Treating an estimate as the count |
estimate is a dry run |
Estimates never substitute for reconciliation; run search |
| Multi-source verify refused |
MULTI_SOURCE_UNSUPPORTED (deferred) |
Verify single-source manifests; split the query |
| Asked for arXiv / literature |
Not shipped yet |
Out of scope; use a literature tool, not Pinakes |
resolve returns ambiguities |
Many-to-many identifier mapping |
Surfaced on purpose, not an error; read ambiguities, pick per authority |
Suggested Next Steps
Pinakes produces the verified input; hand the manifest to a domain skill for
the science:
- Variant work (
clinvar / gnomad / ensembl results) →
genetic-variant-annotation, genetic-target-hypothesis.
- Drug / compound data (
chembl / pubchem / openfda) →
clinicaltrials-landscape, literature-preclinical.
- Structures (
pdb / alphafold) → structure-facing wet-lab skills
(e.g. adaptyv-api for characterization).
- Any dataset →
data-analysis-best-practices before the first model —
carry the manifest as the provenance record.
Always pass along the logical_record_hash + snapshot_version so the next
skill inherits a re-checkable input, not a loose number.
References
Bundled (one level deep, on-demand):
- references/verbs.md — the 8 verbs, chaining, gotchas, and the
pinakes_<verb> ↔ CLI mapping.
- references/manifest-and-completeness.md — completeness + reproducible truth tables, manifest citation fields.
- references/errors.md — the full stable error-code vocabulary grouped by recover-or-stop action.
- references/sources.md — the 26-connector routing table, maturity, license/posture.
- references/setup.md — client-neutral install, generic stdio config,
which pinakes fallback, MCP-or-CLI rule.
- references/recipes.md — Recipes B–F (UniProt, resolve, export, …), each ending in cite-the-manifest, in both MCP + CLI forms.
Scripts:
scripts/verify_and_cite.sh — verify a manifest, and on pass print a paste-ready citation; degrades to a no-op message if pinakes is absent.
Self-describing surface: pinakes catalog and MCP tools/list carry the
live source list, filter schemas, and every verb's parameters — the contract is
in-band. This skill is the judgement layer on top.
1---2name: pinakes-verifiable-retrieval3description: Retrieve reproducible, complete, verifiable records from 26 public biomedical sources (PDB, UniProt, NCBI Protein/Virus/Datasets, ClinVar, Ensembl, gnomAD, PubChem, AlphaFold, ClinicalTrials, openFDA, Open Targets, Reactome, GTEx, GWAS Catalog, and more — run `catalog` for the full list) via Pinakes — the same 8 verbs over an MCP server (pinakes_<verb> tools) or the identical pinakes CLI. Use when an agent must pull biomedical data that has to be reproducible, deterministic, byte-identical, complete-or-fail, or independently verifiable; when a record count, cited fact, or dataset must carry provenance a reviewer can re-derive offline; when pinning a content-addressed snapshot; reading a manifest's completeness or reproducible flag; verifying before citing; or self-correcting a rejected filter. Teaches the judgement the tool schemas do not: pin-or-no-determinism, complete vs best_effort, and verify-before-cite.4---56# Pinakes Verifiable Retrieval78Pinakes is a deterministic, verifiable **input layer** for AI agents over 269public biomedical sources, shipped as one static Go binary that exposes the same108 verbs over an MCP server (`pinakes_<verb>` tools), an identical CLI11(`pinakes <verb>`), and a loopback REST server. Its value is **invisible from the12tool schemas**, which is why this skill exists: every result pins a13content-addressed snapshot (byte-reproducible forever), retrieval is14**complete-or-fail** (reconciled against the source's authoritative total — never15a silent partial), and every result ships a re-runnable **manifest** that16`verify` re-derives **offline**. This skill teaches the judgement layer — *pin or17you get no determinism, read completeness honestly, verify before you cite* — not18the schemas (`tools/list` and `pinakes catalog` already carry field names,19operators, and enums; do not duplicate them here).2021**Boundary.** Pinakes fixes the *input* only. It does **not** run analysis, does22not build evolutionary trees, and **never claims a source is wrong** — the public23databases are authoritative. It fixes naive client retrieval (no imposed order,24no reconciliation, no provenance). It covers **public** sources only. The arXiv25literature corpus is **not yet shipped** — do not assume it. Hand the verified26manifest to a domain skill for the actual science.2728---2930## When to Use This Skill3132✅ **Use when:**33- An agent must pull biomedical data that has to be **reproducible /34 deterministic / byte-identical** across runs.35- You need a **record count** that goes in a methods section and must reproduce36 next month.37- You need a **cited fact** a reviewer can independently re-fetch and check.38- You are handed a **manifest** and must confirm it reproduces before building on39 it (verify-before-cite).40- A query was **rejected** (`UNKNOWN_FIELD`, `BAD_ENUM`, `INVALID_FILTER`,41 `UNSATISFIABLE_QUERY`) and the agent must self-correct rather than retry blind.42- You must know whether a result is **complete vs best_effort vs partial**, or43 whether it is **reproducible** at all.44- You are pulling from PDB, UniProt, NCBI Protein, NCBI Virus, ClinVar, Ensembl,45 gnomAD, PubChem, AlphaFold, ChEMBL, ClinicalTrials, openFDA, cBioPortal, or46 NCBI Datasets and want provenance, not just bytes.4748❌ **Do not use when:**49- The task is **analysis** (differential expression, alignment, tree-building,50 modeling). Use this to *fetch the verified input*, then a domain skill.51- You want to assert a source is **wrong** or compute a "true eternal count" —52 Pinakes does neither; a count is a property of a pin, not of all time.53- The source is **private/internal** or behind a login — Pinakes serves public54 databases only.55- You need the **arXiv** corpus — not shipped yet.56- A raw, throwaway lookup where reproducibility genuinely does not matter (rare —57 if it will be cited or re-run, it matters).5859---6061## Clarification Questions6263### 1. ⚠️ CRITICAL: ASK THIS FIRST — Will this result be reported, cited, or re-run?6465If **yes**, you MUST pin a concrete `snapshot_version` (`sha256:…`, never empty,66never `latest`) and `verify` the manifest **before** citing. An unpinned result is67served from `latest` ⇒ `reproducible:false`, and a re-run next month silently68returns a different set — so a bare count or fact with no pin is not a fact you can69cite. If the answer is genuinely **no** (a throwaway lookup nobody will repeat),70you may skip the pin — but confirm that explicitly, because this is rare.7172### 2. Which source, and is it materializable?7374- Which of the 26 sources answers the question (run `catalog` to confirm the id75 and its filter schema)?76- Is that source **L2+** and **HostServe**? A copyleft source (**ChEMBL**, served77 live) can **never** be `reproducible:true` — if the task demands reproducibility,78 route to a materializable source instead (see *Picking a Connector*).7980### 3. Does the answer have to be complete, or is best-effort acceptable?8182- If it must be the **whole** answer, **do not pass a `limit`** — a cap below the83 authoritative total downgrades the result to `best_effort` (it cannot be84 reconciled). Let complete-or-fail reconcile.85- If a sample is fine, label it `best_effort` honestly — never present a capped or86 best-effort set as "all matching records".8788---8990## Setup & Surface Detection9192Pinakes is **client-neutral**: the same 8 verbs are an MCP tool *and* a CLI93subcommand. Detect the surface and degrade gracefully.9495**Rule:** *use the MCP `pinakes_<verb>` tool if it is present in your tools; else96shell out to the identical `pinakes <verb>` CLI.* Every recipe below is shown in97both forms — they are interchangeable.9899- **MCP present** (tools named `pinakes_catalog`, `pinakes_search`, …): call them100 directly. Generic stdio registration is just:101 ```json102 {"mcpServers": {"pinakes": {"command": "pinakes", "args": ["mcp"]}}}103 ```104 No token, no Docker, no `env`. If a GUI client can't find `pinakes` on its105 `PATH`, replace `"command": "pinakes"` with the absolute path from106 `which pinakes`.107- **CLI only** (no MCP tools, but `which pinakes` resolves): run `pinakes <verb>`108 with the same arguments. Pass a manifest as `--manifest @manifest.json`.109- **Neither** (`which pinakes` is empty): you cannot fetch. Say so plainly and110 stop — do not fabricate records or a manifest. Point the user at install (depth111 → [references/setup.md](references/setup.md)).112113Confirm wiring with `pinakes catalog` (CLI) or `pinakes_catalog` (MCP) before the114first real query. Depth: [references/setup.md](references/setup.md).115116---117118## The Core Loop (1–8)119120Run this loop for any retrieval that will be reported, cited, or re-run. The121hinge steps are **4 (pin a snapshot)**, **5 (read the manifest)**, and **7122(verify)** — skip any one and the determinism guarantee silently evaporates.1231241. **Catalog.** `pinakes_catalog` / `pinakes catalog` to list sources (id,125 maturity, license); scope to one source to get its **filter schema** (fields,126 operators, enums). This is how you learn what is queryable.1272. **Validate locally.** Check every filter field/operator/value against that128 schema *before* any network call. A bad one is rejected locally with a precise129 code (`UNKNOWN_FIELD` / `BAD_ENUM` / `INVALID_FILTER`) — fix it here, cheaply.1303. **Estimate (optional).** `pinakes_estimate` / `pinakes estimate` for a dry-run131 record/byte/call count. **An estimate never substitutes for reconciliation** —132 it is a cost preview, not a completeness claim.1334. **Retrieve WITH a pinned `snapshot_version`.** `search` / `get` / `resolve`,134 always passing a concrete `snapshot_version` (`sha256:…`). **Empty or135 `latest` ⇒ not reproducible.** This is the #1 footgun (see below).1365. **Read Completeness + `reproducible`.** Inspect the manifest: is137 `completeness.state` `complete`? Is `reproducible` `true`? Do not report until138 you have read these — the records alone don't tell you.1396. **Export (optional).** `pinakes_export` / `pinakes export` to materialize to140 parquet/json/fasta/vcf/mmcif. The reproducibility anchor stays the canonical141 NDJSON `logical_record_hash`, **not** the export bytes.1427. **Verify.** `pinakes_verify` / `pinakes verify --manifest @manifest.json`143 re-derives the count and record hash from the pinned snapshot **offline**.144 Exit 0 / `verified:true` ⇒ it reproduces. A refusal ⇒ stop, do not cite.1458. **Cite the manifest.** The citation *is* the manifest: `source_id`, pinned146 `snapshot_version`, `logical_record_hash`, `completeness.state`, and per-source147 `license`/attribution. Hand this to the next skill.148149**Copy-able checklist:**150```151[ ] 1. catalog → confirmed source + read filter schema152[ ] 2. filters validated locally (no UNKNOWN_FIELD / BAD_ENUM / INVALID_FILTER)153[ ] 3. (estimate, if cost matters — not a completeness claim)154[ ] 4. snapshot_version pinned (concrete sha256:… — NOT empty, NOT "latest")155[ ] 5. manifest read: completeness.state == complete? reproducible == true?156[ ] 6. (export, if a file is needed — hash is over NDJSON, not export bytes)157[ ] 7. verify → exit 0 / verified:true (offline)158[ ] 8. cited the manifest (source_id + snapshot_version + logical_record_hash159 + completeness + license/attribution)160```161162---163164## Pin or no determinism (the #1 footgun)165166This is the single most common failure. The `snapshot_version` is what makes a167result reproducible. **No pin ⇒ the engine serves `latest` ⇒168`reproducible:false`**, and a re-run next month silently returns a different set.169A bare count or fact with no pin is not a fact you can cite.170171A manifest is `reproducible:true` **only when ALL of these hold** (the engine's172`SetReproducible` enforces every one — you cannot bypass it):173174| # | Condition | Source of truth |175|---|-----------|-----------------|176| 1 | Served from a **pinned snapshot** (`served_from` = `snapshot@…`) | `ServedFrom.DeterminismClaimable()` |177| 2 | Completeness is **`complete`** and reconciled (count == authoritative) | `Completeness.Reconciled()` |178| 3 | At least one source, and **every** source is **L2+** (any L0/L1 ⇒ false) | `MaturityLevel.DeterminismClaimable()` |179| 4 | **Every** source pins a concrete snapshot — **not empty, not `latest`** | `SetReproducible` |180181If any row fails, `reproducible` is `false` — honestly. The fix is almost always182row 1/4: **pass a `snapshot_version`.** Full table:183[references/manifest-and-completeness.md](references/manifest-and-completeness.md).184185---186187## Reading the Manifest188189The manifest is the product. Read it; never infer from the record list.190191**Completeness decision table** (`completeness.state`):192193| State | Means | What you may say | Action |194|-------|-------|------------------|--------|195| `complete` | Retrieved count **reconciled** against the source's authoritative total and they **match** | "the whole answer (N/N)" | Safe to cite as complete |196| `best_effort` | No trustworthy authoritative total exists (carries a mandatory `reason`) | "a best-effort set, completeness unproven" — **never** "all" | Report the `reason`; do not claim complete |197| `partial` | A multi-source query where ≥1 upstream failed (has `reason` + `resume_cursor`) | "partial; some upstream failed" | Resume via cursor or report the gap |198199**The limit footgun.** A `limit` below the source's authoritative total **cannot200be reconciled**, so the engine downgrades the result to **`best_effort`** — not201`complete`. If you need a `complete` answer, **drop the cap** and let202complete-or-fail reconcile, or report honestly that the set is `best_effort`.203Treating a `limit`-capped set as "all matching records" is a classic error.204205Key manifest fields to cite: `manifest_schema_version`, `engine_version`, per-206source `snapshot_version` + `maturity` + `license`, `logical_record_hash` (the207SHA-256 over canonical NDJSON — the anchor), `completeness`, `served_from`, and208`reproducible`. Depth:209[references/manifest-and-completeness.md](references/manifest-and-completeness.md).210211---212213## Picking a Connector214215Short routing table (26 sources). **L0** = catalog-only (not queryable); **L1** =216queryable but **never** reproducible (live proxy); **L2+** = reproducible against217a pinned snapshot. All 26 shipped connectors are **L2**. `catalog` is the live218source of truth; this table is the at-a-glance version, the full one with SPDX and219attribution flags lives in [references/sources.md](references/sources.md).220221| Source id | Domain | Maturity | License posture |222|-----------|--------|----------|-----------------|223| `pdb` | structures | L2 | public-domain → materializable |224| `uniprot` | proteins | L2 | attribution (CC-BY-4.0) → materializable |225| `ncbi-protein` | sequences | L2 | public-domain → materializable |226| `ncbi-virus` | viruses | L2 | public-domain → materializable |227| `ncbi-datasets` | genes | L2 | public-domain → materializable |228| `clinvar` | variants | L2 | public-domain → materializable |229| `ensembl` | genomics | L2 | no-restrictions → materializable |230| `gnomad` | variants | L2 | public-domain → materializable |231| `pubchem` | compounds | L2 | public-domain → materializable |232| `alphafold` | structures | L2 | attribution (CC-BY-4.0) → materializable |233| `clinicaltrials` | clinical_trials | L2 | public-domain → materializable |234| `openfda` | drug_labels | L2 | public-domain → materializable |235| `opentargets` | target-disease | L2 | public-domain → materializable |236| `reactome` | pathways | L2 | public-domain → materializable |237| `interpro` | protein_families | L2 | public-domain → materializable |238| `complex-portal` | protein_complexes | L2 | public-domain → materializable |239| `rhea` | reactions | L2 | attribution (CC-BY-4.0) → materializable |240| `gene-ontology` | ontology | L2 | attribution (CC-BY-4.0) → materializable |241| `gtex` | expression | L2 | IndexProxy (`redistribute:false`) — see note |242| `gwas-catalog` | gwas | L2 | IndexProxy (`redistribute:false`) — see note |243| `jaspar` | tf_motifs | L2 | IndexProxy (`redistribute:false`) — see note |244| `monarch` | phenotypes | L2 | IndexProxy (`redistribute:false`) — see note |245| `hpo` | phenotypes | L2 | IndexProxy (`redistribute:false`) — see note |246| `cbioportal` | cancer_genomics | L2 | IndexProxy (`redistribute:false`) — see note |247| `chembl` | drugs | L2 | **copyleft (share-alike) → served LIVE, `reproducible:false`** |248| `guide-to-pharmacology` | pharmacology | L2 | **copyleft (share-alike) → served LIVE, `reproducible:false`** |249250**⚠️ Copyleft caveat (ChEMBL, Guide to PHARMACOLOGY).** These two are251Creative Commons Attribution-ShareAlike (copyleft / share-alike). The engine252**refuses to materialize** a share-alike source, so **every result from them is253served live and is `reproducible:false`** by construction — no pin makes it254reproducible. Carry the attribution string; for a reproducible drug-data answer,255prefer a materializable source. Full routing, license postures, and attribution256rules: [references/sources.md](references/sources.md).257258---259260## Worked Recipe A — Ebola, reproduced (flagship)261262Goal: the **complete-genome records for *Zaire ebolavirus*** in NCBI Virus,263pinned, complete, and offline-verified. Exact values from the repo case study.264265**MCP form:**266```json267{268 "name": "pinakes_search",269 "arguments": {270 "source_id": "ncbi-virus",271 "filters": [272 { "field": "organism_taxon_id", "operator": "eq", "value": "186538" },273 { "field": "complete_only", "operator": "eq", "value": "true" },274 { "field": "released_since", "operator": "gte", "value": "2023-01-01" }275 ],276 "snapshot_version": "sha256:b1983c09cd5c70137db4724494d2efaf263baa88c795b4ff1a457ba82fe72f90"277 }278}279```280Then `pinakes_verify` with the returned manifest.281282**CLI form (identical):**283```sh284PIN=sha256:b1983c09cd5c70137db4724494d2efaf263baa88c795b4ff1a457ba82fe72f90285pinakes search --source-id ncbi-virus \286 --filters organism_taxon_id:eq:186538 \287 --filters complete_only:eq:true \288 --filters released_since:gte:2023-01-01 \289 --snapshot-version "$PIN" # → 32 records, Complete (32/32)290pinakes verify --manifest @manifest.json # → {"verified": true}, offline291```292293**Result (this pin):** 32 records, `completeness.state = complete` (32 == 32294authoritative), `logical_record_hash = sha256:01fffd0c…`, NCBI datasets version295`18.30.1`, `reproducible: true`. `verify` re-derives the count and hash from the296snapshot **offline** — a shortened or altered snapshot is refused297(`COMPLETENESS_DRIFT` / `HASH_MISMATCH`).298299**Cite it like this:** "NCBI Virus, complete-genome records for taxon 186538300released since 2023-01-01; snapshot `sha256:b1983c09…`; 32 records, Complete301(32/32); `logical_record_hash sha256:01fffd0c…`; verified offline." The "32" is a302property of **this pin**, not an eternal count — NCBI grows daily; the pin always303yields this set. More recipes (UniProt, resolve, export, multi-filter):304[references/recipes.md](references/recipes.md).305306---307308## Error Handling309310Stable, surface-independent codes (identical across MCP/CLI/REST). Group them by311**action**, not by name:312313| Band | Codes | Action |314|------|-------|--------|315| **Self-correct locally** | `UNKNOWN_FIELD`, `BAD_ENUM`, `INVALID_FILTER` | Re-read the catalog filter schema; fix the field / value / operator **before** any network call |316| **Self-correct the query** | `UNSATISFIABLE_QUERY` | Caller-fixable (maps to 4xx/422); read the message, **change** the query (add a co-filter, shrink the window) — **never retry unchanged** |317| **Pin / input problems** | `UNKNOWN_SOURCE`, `SNAPSHOT_NOT_FOUND`, `UNPINNED_SOURCE`, `IMMATURE_SOURCE` | Fix the source id or supply a valid pinned `snapshot_version`; an L0/L1 source cannot be reproducible |318| **Verify refusals (manifest input)** | `MANIFEST_NIL`, `NOT_REPRODUCIBLE`, `NO_SOURCES`, `MULTI_SOURCE_UNSUPPORTED` | The manifest can't be verified as posed; report it — do not fabricate a pass |319| **Drift refusals — a FEATURE** | `HASH_MISMATCH`, `COMPLETENESS_DRIFT`, `SPEC_DRIFT`, `FILTER_DRIFT`, `SERIALIZER_DRIFT`, `SCHEMA_DRIFT`, `GRAPH_DRIFT`, `PROVENANCE_DRIFT`, `SERVED_FROM_DRIFT`, `LICENSE_DRIFT`, `ENGINE_MISMATCH`, `MANIFEST_SCHEMA_DRIFT`, `QUERY_SNAPSHOT_MISMATCH`, `MANIFEST_DRIFT` | **Stop and report the exact code.** A drift refusal caught a real reproducibility problem; it is not a bug to retry around |320| **Fallback** | `INTERNAL` | Unexpected; report verbatim, do not paper over |321322A `verify` that returns a drift code is doing its job — refusing a result that323does **not** reproduce. Full ~28-code table grouped by recover-or-stop:324[references/errors.md](references/errors.md).325326---327328## Decision Points329330| Decision | Default | Override when |331|----------|---------|---------------|332| Pin a `snapshot_version`? | **Always** | Never skip if the result is reported/cited/re-run |333| MCP tool vs CLI | MCP `pinakes_<verb>` if present | CLI `pinakes <verb>` when no MCP tool; same args |334| Apply a `limit`? | **No** (limit ⇒ `best_effort`) | Only for a deliberate sample you label non-complete |335| `estimate` before `search`? | Skip for small queries | Use when cost/size is unknown — not a completeness claim |336| Verify before citing? | **Always** | No exception — verify is the gate |337| Use ChEMBL for a reproducible answer | **No** (served live) | Use only when live drug data is acceptable; carry attribution |338| Accept `best_effort` | Only if labeled as such | Never present `best_effort`/`partial` as "all" |339340---341342## Common Issues343344| Issue | Cause | Solution |345|-------|-------|----------|346| `reproducible:false` on a clean query | No `snapshot_version` (served `latest`) | Pass a concrete `sha256:…` pin |347| Count differs on re-run | Ran against `latest`; source grew | Pin a snapshot; report count as a property of the pin |348| Result marked `best_effort` unexpectedly | A `limit` below the authoritative total | Drop the `limit`; let complete-or-fail reconcile |349| `IMMATURE_SOURCE` on verify | Source is L0/L1 (not L2+) | Reproducibility is impossible below L2; pick an L2+ source |350| ChEMBL never reproducible | Copyleft (share-alike) → served live | Expected; carry attribution, prefer a materializable source for reproducibility |351| `UNSATISFIABLE_QUERY` loops | Retried identical filters | Read the message; change the query (co-filter, smaller window) |352| `UNKNOWN_FIELD` / `BAD_ENUM` | Filter not in the source schema | Re-read `catalog --source-id <id>`; fix locally before the call |353| `SNAPSHOT_NOT_FOUND` | Pinned a snapshot not in the store | Use a snapshot the catalog/case study actually pins |354| `HASH_MISMATCH` / `COMPLETENESS_DRIFT` on verify | Snapshot shortened/altered, or drift | **Stop**; do not cite — the refusal is correct |355| MCP client can't find `pinakes` | Not on the GUI app's `PATH` | Use the absolute path from `which pinakes` in the config |356| Export bytes differ but hash matches | Export format isn't the anchor | The anchor is the NDJSON `logical_record_hash`, not export bytes |357| Treating an estimate as the count | `estimate` is a dry run | Estimates never substitute for reconciliation; run `search` |358| Multi-source verify refused | `MULTI_SOURCE_UNSUPPORTED` (deferred) | Verify single-source manifests; split the query |359| Asked for arXiv / literature | Not shipped yet | Out of scope; use a literature tool, not Pinakes |360| `resolve` returns ambiguities | Many-to-many identifier mapping | Surfaced on purpose, not an error; read `ambiguities`, pick per authority |361362---363364## Suggested Next Steps365366Pinakes produces the **verified input**; hand the manifest to a domain skill for367the science:3683691. **Variant work** (`clinvar` / `gnomad` / `ensembl` results) →370 `genetic-variant-annotation`, `genetic-target-hypothesis`.3712. **Drug / compound data** (`chembl` / `pubchem` / `openfda`) →372 `clinicaltrials-landscape`, `literature-preclinical`.3733. **Structures** (`pdb` / `alphafold`) → structure-facing wet-lab skills374 (e.g. `adaptyv-api` for characterization).3754. **Any dataset** → `data-analysis-best-practices` **before** the first model —376 carry the manifest as the provenance record.377378Always pass along the `logical_record_hash` + `snapshot_version` so the next379skill inherits a re-checkable input, not a loose number.380381---382383## References384385**Bundled (one level deep, on-demand):**386- [references/verbs.md](references/verbs.md) — the 8 verbs, chaining, gotchas, and the `pinakes_<verb>` ↔ CLI mapping.387- [references/manifest-and-completeness.md](references/manifest-and-completeness.md) — completeness + reproducible truth tables, manifest citation fields.388- [references/errors.md](references/errors.md) — the full stable error-code vocabulary grouped by recover-or-stop action.389- [references/sources.md](references/sources.md) — the 26-connector routing table, maturity, license/posture.390- [references/setup.md](references/setup.md) — client-neutral install, generic stdio config, `which pinakes` fallback, MCP-or-CLI rule.391- [references/recipes.md](references/recipes.md) — Recipes B–F (UniProt, resolve, export, …), each ending in cite-the-manifest, in both MCP + CLI forms.392393**Scripts:**394- `scripts/verify_and_cite.sh` — verify a manifest, and on pass print a paste-ready citation; degrades to a no-op message if `pinakes` is absent.395396**Self-describing surface:** `pinakes catalog` and MCP `tools/list` carry the397live source list, filter schemas, and every verb's parameters — the contract is398in-band. This skill is the judgement layer on top.