Daily workflows with the paper-trail plugin.
- Installation
- Initial configuration
- Creating a literature review without fabricated citations
- Auditing an existing SOTA or paper
- Daily registry maintenance
- Shadow libraries opt-in
- When acquisition is only temporarily stuck
- Integrity hooks
- Troubleshooting
In a Claude Code session:
/plugin install file:///path/to/paper-trail
Or via marketplace (when published):
/plugin marketplace add roomi-fields
/plugin install paper-trail
Verify:
/plugin list # paper-trail v0.1.0 should appear
Environment variables (set in your shell profile or
<project>/.env):
# Vault paths (defaults: ~/research_vault and subdirectories)
export RESEARCH_VAULT_PATH=/path/to/your/vault
export RESEARCH_SOURCES_PATH=$RESEARCH_VAULT_PATH/sources
export RESEARCH_REGISTRY_PATH=$RESEARCH_SOURCES_PATH/_registry
# Vault layout (defaults: obsidian)
export RESEARCH_VAULT_LAYOUT=obsidian # obsidian | flat | zotero (V2)
# Shadow libraries (see §6) — strict opt-in
# export RESEARCH_ENABLE_SHADOW_LIBS=1
# Optional: NotebookLM integration
# export RESEARCH_ENABLE_NOTEBOOKLM=1
# Optional: skip the SessionEnd consistency check
# export RESEARCH_SKIP_END_DOCTOR=1External MCPs used (configured independently in ~/.claude/mcp.json
or <project>/.mcp.json):
paper-search: multi-platform academic search (used bysota-writer,researcheragent)notebooklm: book corpus (optional)rtfm: local indexing (optional)
None of these MCPs are bundled with paper-trail. The plugin operates in degraded mode without them (you will be prompted to provide references manually).
/paper-trail:new-sota "Petri nets in music notation"
What happens:
- Phase A — Research: the
researchersub-agent queries thepaper-searchMCP across 22 platforms and proposes N candidate references - Human selection: you choose the relevant candidates
- Phase B — Acquisition: the
pdf-cascadeskill downloads PDFs via the 8-source cascade (or 10 if shadow libraries are opt-in enabled) - Phase C — Reading: for each PDF in
page1_validated, the plugin writes structured notes into the reference's markdown body (verbatim abstract, main claims, verbatim quotes) - Phase D — Writing: the SOTA is produced citing only validated references. A final "Discarded references" section lists rejected candidates with reasons.
Mechanical safeguards:
- If more than 30% of candidates fail to reach
page1_validated, the plugin refuses to write the SOTA (signal that the topic is too vague or that the cascade is failing) - The
PreToolUsehook refuses writing if any citation points to an unvalidated reference
/paper-trail:new-sota "GPT transformers for symbolic music" --max-candidates 20
See skills/sota-writer/SKILL.md for the full 4-phase workflow.
/paper-trail:audit-sota path/to/SOTA_Existing.md
Output: report classifying each cited reference by state (OK, TO_VALIDATE, HALLUCINATION, UNKNOWN, INACCESSIBLE).
With auto-purge of hallucinations:
/paper-trail:audit-sota path/to/SOTA_Existing.md --purge
Removes wikilinks to retracted references, adds a footer note
listing what was removed, saves to .bak.
Per-citation audit against local PDFs:
/paper-trail:audit-article path/to/Paper.tex
Output: RECEIPTS.md adjacent to the source file, classifying each
citation as VALID / ADJUST / INVALID / UNVERIFIABLE with evidence
quoted from the source.
With inline warnings in .tex.bak:
/paper-trail:audit-article path/to/Paper.tex --warn
Inserts \todo[color=red]{REF AUDIT: <verdict> — <reason>} adjacent
to each problematic \cite{key}.
Faster, does not query paper-search / Crossref:
/paper-trail:receipts path/to/Paper.tex
/paper-trail:status
Counts of references per FSM state (active, waiting, blocked, terminal).
/paper-trail:doctor
/paper-trail:doctor --severity error # errors only
/paper-trail:doctor --fix --severity warn # safe auto-fix
/paper-trail:doctor --correlate-rtfm # RTFM correlation invariants
/paper-trail:doctor --check-sha # recompute sha256 (slow)
/paper-trail:cascade <slug> # one specific ref
/paper-trail:cascade --state candidate --limit 50
/paper-trail:cascade --ref <slug> --dry-run
After RTFM has finished indexing OCR on awaiting_rtfm_ocr refs:
/paper-trail:reactivate-ocr
DISCLAIMER.md before enabling.
Sci-Hub and Anna's Archive are disabled by default. To enable:
export RESEARCH_ENABLE_SHADOW_LIBS=1On the first cascade load of the session, a disclaimer is printed to stderr.
All shadow-library acquisitions are prefixed _optin in the registry
(acquisition_attempts[].via = scihub_optin, annas_archive_optin or
annas_headful_optin) for traceability.
Valid for the duration of the parent shell. To disable:
unset RESEARCH_ENABLE_SHADOW_LIBSAnna's Archive now answers the plain HTTP route with an anti-bot challenge, and its file server rejects a headless browser. A third source drives a windowed Chromium instead — which needs no screen: a virtual display in a container is enough.
It joins the cascade only when Playwright and a display are both available. When they are not, it prints one line to stderr at cascade build time and the other sources carry on unchanged:
[cascade] annas_headful source unavailable (no_display_run_under_xvfb) — …
That line is informational, not an error. To make the source available:
pip install playwright && playwright install chromium
RESEARCH_ENABLE_SHADOW_LIBS=1 xvfb-run -a python -m pipeline run --loopBecause slots are rationed, one reference can take up to
RESEARCH_ANNAS_HEADFUL_BUDGET_S (600 s by default) before the source
gives up and the reference is retried later.
A full pass walks every waiting reference and replays each known failure. When you only want the three you just added, name them:
python -m pipeline run --ref smith_2019_beat,doe_2021_entrainmentSee ACQUISITION_HEADFUL.md for the container
recipe, the scheduled-job command and the things to watch for.
A reference whose cascade failed for reasons that time alone will fix — a mirror rationing its slots, an open circuit-breaker, a 502 — is not locked for human arbitration. It gets two frontmatter fields instead:
| Field | Meaning |
|---|---|
retry_after |
UTC timestamp before which the pipeline skips this reference |
transient_retries |
Consecutive waits so far — drives the back-off |
The wait starts at 15 minutes and doubles on each consecutive failure, up to 8 hours; both fields are cleared as soon as the PDF is acquired. Such a reference resumes on its own, so it needs no action — it will simply be skipped by any pass running before its retry date.
A reference that hit a definitive failure (404, no DOI, page 1 rejected,
a file already refused once) still gets blocked_by: cascade_exhausted_needs_manual and waits for your arbitration, as before.
To lift those automatic locks in bulk — after adding a source, typically — and retry them in the same pass:
python -m pipeline run --retry-exhaustedLocks placed by a human are left untouched. This is a one-off gesture: in a recurring job it replays the full cascade, on every run, over references whose exhaustion has already been established.
Three hooks built into the plugin:
Refuses writing/editing a SOTA_*.md file (Obsidian layout) or
sotas/*.md file (flat layout) if any citation points to an
unvalidated reference.
Block reasons:
- Reference absent from the registry
- Reference in
candidate,uid_resolved,pdf_acquired,needs_reacquisition,blocked_human:*, orretracted
To unblock:
/paper-trail:cascade <slug>to acquire the missing reference- Or remove the offending wikilink from the SOTA
After each edit of a _registry/refs/*.md file, the plugin runs a
mini-doctor on that reference and prints warnings to stderr
(non-blocking).
At the end of each Claude Code session, the plugin runs
pipeline doctor --severity error and prints the summary to stderr.
Skip via export RESEARCH_SKIP_END_DOCTOR=1.
The WorkerLock prevents concurrent mutating sessions. Check no
other pipeline run is running:
ps aux | grep "pipeline run"If a zombie process is detected, the lock auto-releases on the next attempt (via PID liveness check).
I8 = state_history non-monotonic. Likely a migration artifact or
a manual mutation. Auto-fix not available (structural signal).
Investigation:
python -m pipeline doctor --json | jq '.violations[] | select(.invariant=="I8")'RTFM OCR job pending. Check:
rtfm check --slug <slug> -f json
rtfm failed -f json | jq '.failures[] | select(.filepath | contains("<slug>"))'If OCR genuinely failed: manually transition to
needs_reacquisition to retry the cascade with a text source.
Two different situations, distinguished by the reference's frontmatter:
retry_afterset — the reference is waiting, not stuck. It hit only transient unavailability and will resume on its own after that timestamp. Nothing to do. See §7.blocked_by: cascade_exhausted_needs_manual— the cascade genuinely ran out, and your arbitration is needed:
/paper-trail:decide <slug> # inspect and decide one reference
python -m pipeline run --retry-exhausted # or retry them all in bulkIf neither field is set and the state is terminal (page1_validated,
sota_cited_confirmed, retracted), there is simply nothing left to do
on that reference.
The per-source circuit breaker opens after N=5 consecutive failures within a 60-second window. The worker continues with the other sources.
To reset: end the current session (breakers are in-memory, not persisted).
Check environment variables:
echo $RESEARCH_VAULT_PATH
echo $RESEARCH_SOURCES_PATH
echo $RESEARCH_REGISTRY_PATHIf unset, defaults assume a specific development layout. For any
other vault, set RESEARCH_VAULT_PATH at minimum.
docs/ARCHITECTURE.md— system overviewdocs/LEGAL.md— licenses and attributionsDISCLAIMER.md— shadow librariesACQUISITION_HEADFUL.md— browser-based acquisition in a headless containerpipeline/USAGE.md— underlying worker CLIpipeline/ARCHITECTURE.md— FSM and cascade detail