Online Literature Discovery & User-Controlled Import¶
While local library retrieval allows scholars to query existing collections, comprehensive research often requires discovering literature beyond the local library. Traditional academic search tools force researchers into disjoint workflows: searching external databases, manually downloading PDFs, re-importing into reference managers, and re-triggering embedding pipelines.
Zocrates unifies literature discovery directly inside conversational dialogue through the autonomous Online Literature Discovery and User-Controlled Import Pipeline. This closes the complete research loop: Query → Prospect → Explain → Select → Import → Batch Convert & Index.
1. System Architecture & Symmetrical Multi-Agent Design¶
The online search system mirrors the local search architecture symmetrically, utilizing Google ADK dynamic workflows (@node) and exact isolation scopes to prevent event pollution:
graph TD
subgraph "Single Unified Conversational Layer"
U[User Prompt / Query / Pasted DOI] --> C["coordinator_agent<br/>(zocrates/agents/coordinator/)"]
C -->|Question on existing library| TL["search_library tool"]
C -->|Explore new external literature| TO["search_online_literature tool"]
end
subgraph "Local Search Pipeline (zocrates/agents/library_search/)"
TL --> LP[search_library_pipeline_node]
LP --> LS[library_search_agent: Chroma + FTS5]
LS --> LSYN[library_synthesizer_agent: grounded QA + citations]
end
subgraph "Online Search Pipeline (zocrates/agents/online_search/)"
TO --> DP[search_online_pipeline_node]
DP --> DS[online_search_agent: OpenAlex ReAct]
DS --> DD[(In-Memory Dedup vs zotero.sqlite: Filter Existing)]
DD --> DSYN[online_synthesizer_agent: inquiry overview + candidate selection]
end
subgraph "Frontend UI & User Control (sidebar.js)"
DSYN -->|SSE Stream / metadata.candidates| UI[Candidate Papers Checklist: Default ALL UNSELECTED]
UI -->|User checks papers + clicks Import Selected| MODAL[Confirmation Dialog: Titles + OA Status + Target Collection]
MODAL -->|Confirm & Import| BE_STG[FastAPI: POST /discover/prepare_batch]
end
subgraph "Plugin Write-Back & Ingestion Pipeline"
BE_STG -->|Staged PDFs| ZP[scholarImport.js: saveTx + Attachments.importFromFile]
ZP -->|New Attachment Keys| BC[BatchConverter.startBatchConvert: POST /batch-convert]
BC --> RAG[(OKF Bundles + VLM Captions + ChromaDB/FTS5)]
end
Symmetrical Pipeline Naming & Scopes¶
| Role | Local Pipeline | Online Pipeline | Scope Identifier |
|---|---|---|---|
| Pipeline Tool | search_library |
search_online_literature |
— |
| Workflow Node | search_library_pipeline_node |
search_online_pipeline_node |
search_online_pipeline:{fc_id} |
| Retrieval Agent | library_search_agent |
online_search_agent |
Isolated subagent |
| Synthesis Agent | library_synthesizer_agent |
online_synthesizer_agent |
Context-continuous |
- Coordinator Agent (
coordinator_agent): Analyzes the user's intent. When the user asks about literature outside their library, requests new papers, or pastes an external DOI, the coordinator delegates retrieval tosearch_online_literature. - Online Search Agent (
online_search_agent): Operates with"low"thinking effort, autonomously formulating queries, inspecting candidate metadata, and retrieving candidate works via scholarly academic APIs. - Online Synthesizer Agent (
online_synthesizer_agent): Operates with"high"thinking effort, providing a grounded overview of what the discovered candidate papers contribute, their core methodologies, and why they answer the user's inquiry.
2. Scholarly Discovery Engine (zocrates/discovery/)¶
The discovery engine orchestrates external academic providers, normalizes paper metadata, and validates Open Access PDF availability.
graph LR
subgraph "Discovery Engine Providers"
OA[OpenAlexProvider: Primary Search 250M+ Works]
UP[UnpaywallResolver: Legal OA PDF URLs]
CR[CrossrefValidator: DOI Verification]
AF[AbstractFallback: Semantic Scholar / PubMed]
end
OA --> DEDUP[Deduplicator vs zotero.sqlite]
UP --> STAGE[Atomic PDF Stager]
CR --> DEDUP
AF --> DEDUP
2.1 OpenAlex as Primary Search Backbone¶
OpenAlex indexes over 250 million scientific works across all academic disciplines and synchronizes arXiv preprints on a daily cadence. Zocrates uses OpenAlex as its primary discovery backbone:
- Bypasses the 3-second courtesy delays of legacy single-repository APIs.
- Employs inverted-index abstract reconstruction (
abstract_inverted_index) to recover structured abstracts with zero publisher website scraping. - Captures high-confidence scored concepts (
keywordswith confidence scores \(\ge 0.60\)) and primary research topics.
2.2 Unpaywall & Crossref Integration¶
- Unpaywall (
UnpaywallResolver): Resolves legal, author-manuscript, and publisher Open Access PDF locations for discovered papers with verified licenses (CC-BY, CC0, etc.). - Crossref (
CrossrefValidator): Validates DOIs, normalizes publisher metadata, and backfills missing publication dates and container titles. - Opportunistic Abstract Backfill: If an abstract is missing from OpenAlex, a fast 3-tier cascade queries Semantic Scholar or PubMed E-utilities with a 2.0s timeout before falling back to OpenAlex concept tags. Publisher landing pages are never scraped.
3. Strict Novelty Filtering (Local Library Deduplication)¶
To prevent cluttering chat with papers the user already owns, the discovery engine executes in-memory deduplication against the user's local zotero.sqlite database:
graph TD
RAW[Raw Candidates from Scholarly APIs] --> C1{Matches local DOI?}
C1 -- Yes --> DROP[Filter Out: Already in Library]
C1 -- No --> C2{Matches local arXiv ID?}
C2 -- Yes --> DROP
C2 -- No --> C3{Title Levenshtein Similarity >= 0.88?}
C3 -- Yes --> DROP
C3 -- No --> PASS[Novel Candidate Retained for UI Card]
Deduplication Tiers¶
- DOI Normalization: Normalizes prefixes and lowercase canonical strings (
10.xxxx/...), checking against all registered identifiers inzotero_db. - arXiv ID Normalization: Strips version suffixes (
v1,v2) to match preprints against published versions (arXiv:2305.12345). - Normalized Title Levenshtein Distance: Normalizes case, removes punctuation, and evaluates normalized Levenshtein similarity. Titles with \(\ge 0.88\) similarity are treated as duplicates.
- Re-check on Import: Candidate IDs are re-checked at confirmation time in
POST /discover/prepare_batch, ensuring that items imported in another tab or past turns cannot be duplicated.
4. Zero-Friction Polite Pool Etiquette¶
Academic APIs maintain generous rate limits for polite users who provide a valid contact email:
- Automatic Detection: On extension startup,
bootstrap.jschecks Zotero's sync credentials (Zotero.Prefs.get("sync.server.username")orZotero.Users.getCurrentUsername()). If the username contains an email address (@), it is passed to the backend duringPOST /register. - Configuration Storage: Stored as
scholar_polite_emailunderFileLockinzocrates_config.json. - HTTP Header Injection: All requests to OpenAlex and Unpaywall automatically append
mailto:{scholar_polite_email}in query strings or user-agent headers. - Preferences UI: Users can manually inspect or configure their academic email in Zotero Preferences → Zocrates.
5. Candidate Data Contract & Dual Delivery Channel¶
To eliminate hallucinated candidate metadata while keeping synthesis natural:
- Numbered Synthesizer References:
The
online_synthesizer_agentdiscusses candidates using simple index references:- [1] **Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection**: Introduces reflection tokens allowing LLMs to selectively invoke retrieval and verify answers. - [2] **Corrective Retrieval Augmented Generation (CRAG)**: Evaluates retrieved document quality before passing chunks to generation. - Pipeline Delivery Channel:
The Python pipeline intercepts candidate indices, correlates them with discovered candidate metadata, and injects structured records into the final SSE event:
{ "event": "done", "payload": { "metadata": { "candidates": [ { "candidate_id": "cand_01", "title": "Self-RAG: Learning to Retrieve, Generate, and Critique", "authors": ["Akari Asai", "Zeqiu Wu", "Yizhong Wang"], "year": 2024, "doi": "10.48550/arXiv.2310.11511", "openalex_id": "W4387819876", "pdf_url": "https://arxiv.org/pdf/2310.11511.pdf", "oa_status": "gold", "keywords": ["Retrieval-Augmented Generation", "Self-Reflection"] } ] } } } - Session Persistence: Candidate payloads are persisted inside
sessions.dbalongside message events, allowing interactive candidate cards to re-render when switching between sessions.
6. Atomic PDF Staging & Batch Verification (/discover/prepare_batch)¶
When a user selects candidate papers and clicks "Import Selected", the frontend sends a preparation request to POST /discover/prepare_batch:
sequenceDiagram
autonumber
participant UI as Sidebar (sidebar.js)
participant API as FastAPI Backend
participant Staging as Local Disk Staging
participant ZP as Plugin (scholarImport.js)
UI->>API: POST /discover/prepare_batch { candidates: [...] }
loop For each candidate with PDF
API->>API: Download with 15s timeout
API->>API: Validate magic bytes (%PDF-)
API->>API: Enforce 100MB byte limit
API->>Staging: Save to <zocrates-data>/staging/<id>.pdf
end
API-->>UI: Return batch manifest (staged_pdf_path, metadata)
UI->>ZP: scholarImport.importBatch(manifest, targetCollectionId)
Staging Guardrails¶
- Header Magic Byte Verification: Validates the first 4 bytes match
%PDF-. Files failing this check (e.g. HTML error landing pages or Cloudflare captchas) are dropped immediately. - File Size Ceiling: Rejects any file exceeding
100MBto prevent disk exhaustion. - Metadata-Only Fallback: If a paper is not Open Access or the download fails, the paper is imported as a clean metadata-only record (allowing the user to resolve access through institutional proxies later).
7. In-Process Zotero Write-Back Engine (scholarImport.js)¶
Zotero 10 enforces transactional integrity for library mutations. Writing to the library must occur inside Zotero's main process with SystemPrincipal chrome privileges:
7.1 Transactional Item Creation¶
scholarImport.js uses Zotero.DB.executeTransaction() to ensure atomic write-back:
await Zotero.DB.executeTransaction(async () => {
for (const itemData of itemsToImport) {
const item = new Zotero.Item("journalArticle");
item.setField("title", itemData.title);
item.setField("DOI", itemData.doi);
item.setField("date", String(itemData.year));
item.setField("abstractNote", itemData.abstract);
// Attach Creators
item.setCreators(itemData.creators);
// Attach Tags (OpenAlex Keywords >= 0.60)
for (const tag of itemData.keywords) {
item.addTag(tag, 1);
}
await item.saveTx();
// Attach PDF if staged
if (itemData.stagedPdfPath) {
await Zotero.Attachments.importFromFile({
file: itemData.stagedPdfPath,
parentItemID: item.id
});
}
if (targetCollectionId) {
targetCollection.addItem(item.id);
}
}
});
7.2 Race Condition Prevention (_suppressKeys)¶
When Zotero creates a new attachment, autoConverter.js detects the "add" event and automatically queues a single conversion call (POST /convert/{key}).
If scholarImport.js simultaneously launches batch conversion, a race condition occurs. To prevent this:
scholarImport.jstemporarily registers the new attachment keys inAutoConverter._suppressKeys.autoConverter.jsignores the"add"events for suppressed keys.- Once item creation completes,
scholarImport.jsinvokesZotero.Zocrates.BatchConverter.startBatchConvert(attachmentKeys, { skipConfirm: true }). - Suppressed keys are cleared safely in a
finallyblock.
8. Dual-Layer Keyword Preservation¶
Discovered literature preserves rich taxonomic metadata across both Zotero and the RAG storage layers:
graph LR
OA[OpenAlex Scored Keywords >= 0.60] --> ZTAGS[1. Zotero Native Tags: item.addTag]
OA --> FRONT[2. OKF Bundle Frontmatter: keywords: [...]]
FRONT --> META[conversion_meta.db keywords column]
ZTAGS --> GRAPH[Paper Relatedness Graph: sync_shared_tags]
- Zotero Native Tags: High-confidence keywords are saved directly as Zotero item tags (
type = 1), making them immediately filterable in the desktop tag selector. - Paper Relatedness Graph: Native tags feed directly into the offline graph visualizer, immediately connecting the newly imported paper to related items in the library via
shared_tagsedges. - OKF Frontmatter Preservation: During subsequent Markdown conversion, keywords are written into
document.mdYAML frontmatter and indexed in SQLiteconversion_meta.db, bypassing statistical fallback routines.
9. Frontend Checkbox State Retention¶
In sidebar.js, chat message rendering is throttled to 50ms to maintain 60 FPS during fast streaming turns. If candidate checkboxes were re-rendered from scratch on every stream tick, active user clicks would be wiped.
Zocrates preserves user selection state:
- Keyed State Tracking: Checkbox selections are tracked in an in-memory
Set(selectedCandidateKeys). - Non-Destructive DOM Updates: Throttled stream renders preserve the container's
.checkedproperties. - Default Unselected Policy: Candidate cards default to all unselected to ensure users explicitly choose what to add to their library.
- Global Selection Controls: A header toggle (
[Select All / Deselect All]) allows researchers to quickly grab full candidate batches.
10. Configuration & Subagent Role Pinning¶
Online discovery roles are configured in <zotero-data-dir>/zocrates-data/zocrates_config.json:
{
"scholar_polite_email": "researcher@university.edu",
"override_agent_models": {
"online_search": {
"model": null,
"thinking": "low"
},
"online_synthesizer": {
"model": null,
"thinking": "high"
}
}
}
- Both roles default to
model: null, dynamically inheriting the globally active model. - Researchers can surgically pin
online_searchto a fast, low-cost model while routingonline_synthesizerto a frontier reasoning model for rigorous candidate evaluation.