Workflows¶
Operator sequences for one collection slice. A run config (also called a
profile) saves that slice. paperful all runs the usual chain in one
process. Individual commands stay available and can still be chained with
&&.
Run a recipe again when you want another pass. Recipes below are copy-paste
CLI commands (paperful all and named run configs).
Workbench first? Click-by-click jobs (Preview → Grab → Attach, Discover,
Repair) live in Walkthroughs. The same verbs are on
docker compose up / paperful serve: gui.md. The research-pack
(cited works → parents → PDFs) is research-pack.
Do not conflate¶
Word |
What it is |
Where it lives |
|---|---|---|
Run config / profile |
SCOPE plus fetch/write policy for a repeated slice (collection, years, item types, |
|
|
One command that runs a fixed sequence of existing verbs |
|
|
Source policy: open access plus campus EZProxy, no Scholar, no Sci-Hub |
CLI flag or |
Playbook |
URL → PDF rule ( |
|
Pack |
Witness for one executed sequence. Lists child reports. Not a template you re-run |
|
|
Named people list → their new papers (ORCID / OpenAlex). Optional |
|
|
OpenAlex publication-year counts for a query or search/hybrid profile. Read-only (no library) |
CLI + MCP |
|
Creator frequency in |
|
|
Missing PDFs → CSV / RG tabs for author contact. Never fetches, never sends mail |
|
|
Re-run a saved snowball profile; propose unseen works. The rollup is |
|
|
PDF drop folder → attach to matching items; |
|
Grey-lit packs/ and run-config profiles/ are different directories.
state/packs/ is the witness, and it is excluded from backups of the
checkout. Profiles sit next to config.toml so they are kept with config.
1. The shell sequence¶
This is the pattern all copies. SCOPE is shared so every verb sees the
same collection, years, and item types. run adds fetch flags. fix-metadata
and summarize add --apply.
SCOPE=(-C BBNJ -T journalArticle --year-from 2021 --year-to 2026)
uv run paperful gaps "${SCOPE[@]}" \
&& uv run paperful run "${SCOPE[@]}" --try-all --retry-failed --upgrade-linked \
&& uv run paperful lint "${SCOPE[@]}" \
&& uv run paperful fix-metadata "${SCOPE[@]}" --apply \
&& uv run paperful summarize "${SCOPE[@]}" --apply
&& stops on the first non-zero exit. lint is not --strict, so findings
do not abort the chain. summarize needs [llm].enabled; if the model is
off, that step fails and the earlier steps have already finished.
The same flags work one verb at a time. See Commands.
2. Same chain, one process¶
uv run paperful all -C BBNJ -T journalArticle --year-from 2021 --year-to 2026
Default steps, in order:
gapsrun --try-all --retry-failed --upgrade-linkedlint(not--strict)fix-metadata --applysummarize --apply
Those run flags and --apply are the builtin all policy. They apply when
the command is all and the profile does not set the key. paperful run
alone does not turn --try-all on.
The chain stops on the first failing step, same as &&.
Dry run¶
uv run paperful all -C BBNJ -T journalArticle --year-from 2021 --year-to 2026 --dry-run
Step |
What |
|---|---|
|
Counts, as usual |
|
Would-hit table. No downloads |
|
Read-only findings |
|
Proposes patches. Does not pass |
|
Skipped. Summarize has no dry-run and the default destination writes notes |
run --dry-run exits before it writes state/runs/…-run.json, so an
auto-opened pack may list gaps, lint, and fix-metadata and omit run.
That matches a standalone dry run.
LLM off¶
If [llm].enabled is false, all skips summarize and continues. The line
says llm.enabled is false. Pass --require-summarize (or set
require_summarize = true in the profile) to exit 1 instead.
all --dry-run skips summarize even when the LLM is on.
Pack¶
If no pack is open, all opens one (label: --label, otherwise the profile
name, otherwise all) and closes it when the command finishes, including
when a step fails. If a pack is already open, all appends to it and leaves
it open.
PAPERFUL_PACK=off still keeps each child report out of a pack. all does
not open one in that case.
paperful pack show reads the pack (paperful.pack.v1). Each step can include
witness_id when the child report under state/runs/ carried a witness
block. It does not re-run the steps.
Narrow the chain¶
uv run paperful all -C BBNJ --steps gaps,run,lint
uv run paperful all -C BBNJ --skip summarize
--steps replaces the list. --skip removes names from it. Unknown names
exit 1. Optional names you can add, which are not in the default chain:
snapshot, dedupe, synthesize, ocr, gaps_handoff.
dedupe honors apply only when all is running it. A direct
paperful dedupe --profile … does not trash unless you also pass --apply.
3. Save the slice¶
Profiles live beside config.toml, never under state/.
uv run paperful profile save bbnj-journal \
-C BBNJ -T journalArticle \
--year-from 2021 --year-to 2026 \
--try-all --retry-failed --upgrade-linked \
--apply \
--description "BBNJ journal articles 2021–2026"
That writes profiles/bbnj-journal.toml next to the config file in use
(--config if you passed it, otherwise the file Paperful loaded). It does
not rewrite config.toml. A second save exits 1 until --force.
The same keys can live in config instead of a file:
[profiles.bbnj-journal]
description = "BBNJ journal articles 2021–2026"
collections = ["BBNJ"]
types = ["journalArticle"]
year_from = 2021
year_to = 2026
try_all = true
retry_failed = true
upgrade_linked = true
apply = true
If both exist, the file overlays the table key by key. collections in the
file replaces collections in the table; keys only in the table remain.
Then:
uv run paperful all --profile bbnj-journal
uv run paperful profile list
uv run paperful profile show bbnj-journal
profile show prints the merge paperful all would use, including
builtin defaults the file left out. profile show with no name prints those
defaults alone.
A one-off file that is not in profiles/ works too:
uv run paperful all -f ./bbnj-journal.toml
-f / --run-config overlays --profile when both are set.
Use the profile on one verb¶
--profile works on gaps, run, lint, fix-metadata, summarize,
ocr, dedupe, snapshot, restore, and synthesize. A profile that only stores
SCOPE does not turn on try_all for a bare run:
uv run paperful gaps --profile bbnj-journal \
&& uv run paperful run --profile bbnj-journal \
&& uv run paperful lint --profile bbnj-journal \
&& uv run paperful fix-metadata --profile bbnj-journal \
&& uv run paperful summarize --profile bbnj-journal
Put try_all, retry_failed, and upgrade_linked in the profile if run
should use them without extra flags. apply = true makes
fix-metadata --profile and summarize --profile write. It does not make
dedupe --profile trash.
Turn a saved bool off for one invocation:
uv run paperful run --profile bbnj-journal --no-try-all
uv run paperful all --profile bbnj-journal --no-apply
Pairs: --try-all/--no-try-all, --retry-failed/--no-retry-failed,
--upgrade-linked/--no-upgrade-linked, --apply/--no-apply,
--library/--no-library. Passing -C replaces the profile’s collections
and clears library unless you also pass --library.
Precedence¶
Builtin
allpolicy (steps, try-all, retry-failed, upgrade-linked, apply) — only forallandprofile show[profiles.*]inconfig.tomlprofiles/<name>.tomlbeside that config (same name overlays the table)--profile NAMEor-f/--run-config FILEExplicit CLI flags
CLI always wins for a key you passed. Omitted flags keep the profile value,
then the builtin all value when the command is all.
Docker¶
profiles/ is next to config.toml on the data dir (/data in Compose).
Create it beside config, not inside the image:
mkdir -p ../paperful-data/profiles
docker compose run --rm paperful all --profile bbnj-journal --dry-run
Same commands as uv run. Headed session login stays on the host. For
LLM / RAG / browser-agent inside the container, build heavy
(PAPERFUL_IMAGE_MODE=heavy). See Docker.
4. Deep collection pass (example template)¶
Operator recipe for one collection slice: dedupe, aggressive PDF fetch
(including the browser-agent lane), metadata write-back, grounded summaries,
then manual handoff for whatever automation missed. Replace placeholders with
your collection path (name or parent/subcollection), profile name, and pack
label. Subcollections are included automatically when you pass a parent path.
Prerequisites: Zotero 10+ with write API, a real Unpaywall email,
[llm].enabled for summarize and the auto browser_agent lane on run,
uv sync --extra llm --extra browser-agent on the host (Python 3.11+) or a
heavy Compose image, and headed paperful session login ezproxy /
scholar when you use those sources. Optional: [camofox].enabled plus a
running camofox-browser server for a scripted try before the LLM agent
(camofox.md).
Optional: [inbox].dir so --handoff tabs can continue into inbox watch.
Campus-only policy: add --preset eoi on run / all.
paperful all forwards --handoff, --include-doi-tabs, --promote,
--htmlpdf, and the other run-step flags to its internal run. Add
--after-handoff-gaps (or a gaps_handoff step) to run
gaps --list-missing with the same handoff after the chain. The shell
sequence below stays useful when you want dedupe first, or a different
handoff mode after run than after the leftover-gaps pass.
Review duplicates before merging:
uv run paperful dedupe -C COLLECTION --dry-run
# read state/dedupe-packs/*.md
uv run paperful dedupe -C COLLECTION --apply
# title+year merges (optional):
uv run paperful dedupe -C COLLECTION --apply --apply-medium
One-shot chain (dedupe → fetch with relogin + browser agent → lint → metadata
→ summarize → sequential handoff for stragglers). Add year/type filters to
SCOPE when you need them (see scope filters):
SCOPE=(-C COLLECTION)
# SCOPE=(-C COLLECTION -T TYPE --year-from YYYY --year-to YYYY)
uv run paperful pack open --label PACK_LABEL
uv run paperful dedupe "${SCOPE[@]}" --apply && \
uv run paperful gaps "${SCOPE[@]}" && \
uv run paperful run "${SCOPE[@]}" \
--try-all --retry-failed --upgrade-linked \
--browser-agent \
--handoff tabs && \
uv run paperful lint "${SCOPE[@]}" && \
uv run paperful fix-metadata "${SCOPE[@]}" --apply && \
uv run paperful summarize "${SCOPE[@]}" --apply && \
uv run paperful gaps "${SCOPE[@]}" --list-missing --handoff walk
uv run paperful pack close
Same policy as all, with dedupe first and handoff on the run step plus
a leftover-gaps pass:
uv run paperful all -C COLLECTION \
--steps dedupe,gaps,run,lint,fix-metadata,summarize \
--apply --browser-agent \
--handoff tabs --after-handoff-gaps --gaps-handoff walk
Handoff modes: tabs (batch open, optional inbox watch after tabs),
watch (tabs then poll [inbox].dir), walk (one URL at a time via
[gaps].downloads_dir). See Sources (manual handoff after
soft blocks).
Save the slice for reruns. Profile TOML can store handoff /
gaps_handoff; CLI --handoff still overrides one invocation:
uv run paperful profile save PROFILE_NAME \
-C COLLECTION \
--try-all --retry-failed --upgrade-linked \
--apply \
--steps dedupe,gaps,run,lint,fix-metadata,summarize \
--description "DESCRIPTION"
uv run paperful all --profile PROFILE_NAME --browser-agent \
--handoff tabs --after-handoff-gaps --gaps-handoff walk
Dry-run the automated half first:
uv run paperful all -C COLLECTION \
--steps dedupe,gaps,run,lint,fix-metadata,summarize \
--apply --browser-agent --dry-run
(summarize is skipped; dedupe and run do not apply merges or downloads.)
5. Research pack (cited works → parents → PDFs)¶
The operator playbook is research-pack. Short form:
Cited inside collection PDFs, not already in the library. Always dry-run the
gap pack and ingest-dois classify before --apply. Then run (or inbox)
fills PDFs. Then dedupe.
docker compose run --rm paperful refs gap -C BBNJ
# read state/refs-gaps/*/pack.md
docker compose run --rm paperful ingest-dois --from-pack state/refs-gaps/<stamp> -C BBNJ
docker compose run --rm paperful ingest-dois --from-file state/refs-gaps/<stamp>/dois.txt -C BBNJ --apply --tag bbnj
docker compose run --rm paperful run -C BBNJ
docker compose run --rm paperful inbox drain # optional: PDFs dropped into [inbox].dir
docker compose run --rm paperful dedupe -C BBNJ --apply
Briefing / note mentions (DOIs or titles named in markdown or HTML, not
bibliographies inside PDFs): coverage --from-file or --from-note writes
state/coverage/<stamp>/ (paperful.coverage.pack.v1). Always dry-run. Feed
missing rows through the same ingest-dois --from-pack path as refs gap. See
commands.md (coverage, ingest-dois).
Contributors: the same verbs with uv run paperful ….
8. All-in E2E (NBA throwaway)¶
An opt-in harness that chains snowball → ORCID → full fetch → summarize →
Twenty/reachout/handoff on -C e2e/NBA (not production BBNJ). Not in default
CI. See E2E NBA and make e2e-nba.
What this is not¶
Not a workflow engine. Steps are a list, not a graph.
Not Rollup’s cron, and not TranscriptX’s saved GUI profiles.
Not a grey-lit playbook. URL → PDF rules stay in
[[grey_playbooks]].Not a pack. A pack records one run; a profile is the input you can run again.
Not a field author pack.
state/author-packs/is forauthor_site;state/packs/is the run witness.Not a fetch preset.
reachoutnever downloads;--preset oastill grabs.