Batch operation
Running a layer across the corpus is the same command in a loop. What makes it workable is that
the runner knows what has already been done: a layer whose cell is current is skipped, so
re-running a sweep costs nothing for the papers that have not changed.
for paper in $(python3 -c "import yaml;print(' '.join(
yaml.safe_load(open('corpus.yaml'))['corpora']['elife']['papers']))"); do
python3 scripts/pipeline.py run "$paper" claim-tree --note "sweep $(date +%F)"
done
Then look at what happened:
python3 scripts/pipeline.py state
Re-running after a prompt change
This is the case the ledger exists for. Edit extract/prompts/caption-reader.md and every run
that read it becomes stale, because the run recorded the hash of the prompt it used. Staleness
propagates along needs, so the reconciliation, the review, the edges and the tree downstream
of it go blocked — not wrong, but not to be trusted until the thing above them is rebuilt.
state will show you exactly which cells. The sweep above then rebuilds only those.
Before spending anything on a corpus-wide re-run, measure the change on one paper:
evaluate scores a re-extraction against the committed
corpus, so you find out whether the prompt is better before you rebuild ten papers with it.
As a gate
python3 scripts/pipeline.py state --fail-on-stale
Exits non-zero if any cell is stale — a prompt or a script changed and the artifacts built from it have not been rebuilt. Suitable for CI.
It does not fail on absent or unrecorded. Most layers have not been run for most
papers, so a gate that treated an un-run layer as a failure would never be green.
Cost
Per paper, for a typical eLife article:
| Layer | Cost | Wall time |
|---|---|---|
prepare |
$0 — fetch and slice, cached after the first | ~2 s |
| the three readers | ~$4 | ~4 min |
reconcile |
~$1 | ~1 min |
external-review |
~$2 | ~3 min |
edge-inference |
~$0.20 | ~30 s |
claim-tree |
$0 — reads the edges the layer wrote | <1 s |
reference-check |
$0 — CrossRef is free | ~1 s per citation |
coverage, marks |
$0 — no model calls | ~5 s |
| end to end | ~$7 | ~10 min |
A hundred-paper corpus with external review is roughly $700 and seventeen hours run sequentially. Parallelising across papers is safe — the layers for one paper never read another paper's files — and the practical limit is your provider's rate quota, not the pipeline.
Cutting the cost
- Skip external review where role accuracy matters less than throughput: $2 and three minutes per paper. It is the difference between ~65% and ~96% role agreement, so this is a real trade rather than a free saving.
--model-{results,caption,structure}sends the readers to a cheaper model. They read a slice and emit structured claims, which is the least demanding work in the chain.--max-claimscaps per-paper output, for smoke tests.prepareis cached under~/.cache/elife-extract/, so re-running any layer on a paper costs no fetch and works offline.
Papers outside the corpus
The runner acts on papers listed in corpus.yaml, and takes their DOIs from
claims/<paper>/index.md — which a new paper does not have until claim-tree has run. So the
first layer is seeded by hand:
cd extract && python3 -m elife_extract.cli prepare --paper <slug> --doi 10.7554/eLife.<id>
Add the slug to corpus.yaml and the rest of the chain runs normally. For a paper that is not
an eLife DOI at all, --pdf-path takes a local file; expect worse slicing, since a PDF gives a
column-order guess where JATS gives real section boundaries.