This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Research Synergy (ReSyn) is a Rust application for Literature Based Discovery (LBD). It aggregates academic papers from arXiv and InspireHEP, persists them to SurrealDB, constructs knowledge graphs from citation relationships, and visualizes them as interactive force-directed graphs.
The workspace has multiple binaries. Always use cargo run --bin resyn with the appropriate subcommand — bare cargo run is ambiguous.
cargo build # Debug build (all crates)
cargo build --release # Release build
cargo test # Run all tests
cargo test <test_name> # Run a specific test
cargo test -- --nocapture # Run tests with stdout visible
cargo check # Type-check without building
cargo fmt --all -- --check # Check formatting
cargo clippy --all-targets --all-features # Lint (CI runs with -Dwarnings)Subcommands (cargo run --bin resyn -- <subcommand> [args]):
# Crawl
cargo run --bin resyn -- crawl --paper-id 2503.18887 --db surrealkv://./data
cargo run --bin resyn -- crawl --paper-id 2301.12345 --max-depth 2 --db surrealkv://./data
cargo run --bin resyn -- crawl --source inspirehep --paper-id 2301.12345 --db surrealkv://./data
# Analyze (NLP + optionally LLM + gap analysis)
cargo run --bin resyn -- analyze --db surrealkv://./data
cargo run --bin resyn -- analyze --db surrealkv://./data --llm-provider claude
# Export Louvain community graph to JSON (for external tooling, e.g. Kuramoto-LBD notebook)
cargo run --bin resyn -- export-louvain-graph --db surrealkv://./data --output graph.json
cargo run --bin resyn -- export-louvain-graph --db surrealkv://./data \
--output research_synergy_pre2015.json \
--published-before 2014-12-31 \
--tfidf-top-n 50
# Serve web UI
cargo run --bin resyn -- serve --db surrealkv://./data
# Bulk-ingest papers from OpenAlex REST API (Phase RS-08)
# Default filter: ML+stat.ML+NeuralNet papers hosted on arXiv (~1.5M works)
# NOTE: OpenAlex itself does not need a key — the unauthenticated polite pool (mailto UA)
# still works (verified 2026-07-05). But `bulk_ingest::run` currently hard-exits when
# --api-key/OPENALEX_API_KEY is absent, so the flag is required by *this* CLI, not by the API.
cargo run --release --bin resyn -- bulk-ingest --db surrealkv://./data-openalex \
--api-key "$OPENALEX_API_KEY"
# Physics corpus filter (Condensed matter + Statistical physics on arXiv):
cargo run --release --bin resyn -- bulk-ingest --db surrealkv://./data-physics \
--api-key "$OPENALEX_API_KEY" \
--filter "primary_location.source.id:S4306400194,concepts.id:C26873012|C121864883"
# Frontend (separate from the backend binary)
cd resyn-app && trunk serveThe application pipeline: select data source → fetch seed paper → BFS crawl references → persist to SurrealDB → NLP + graph analytics + gap analysis → serve the results in a Leptos web UI (or export JSON for external tooling).
There is no top-level src/. This is a four-crate Cargo workspace (edition 2024, resolver = "2"):
| Crate | Role |
|---|---|
resyn-core |
All domain logic. WASM-safe by default; server-only pieces are behind the ssr feature. |
resyn-app |
Leptos 0.8 frontend (cdylib + rlib); csr and ssr features. |
resyn-server |
The resyn binary (clap subcommands) + axum/leptos_axum SSR host. |
resyn-worker |
WASM web-worker crate (gloo-worker) running the force-layout simulation off the main thread. |
Always available (WASM-safe): analysis, data_processing, datamodels, error, gap_analysis, graph_analytics, nlp, utils, validation.
Behind ssr: data_aggregation, database, llm. (See resyn-core/src/lib.rs — the feature gating is load-bearing: single-clustering pulls in rayon, which is not WASM-compatible.)
-
data_aggregation/(ssr) — source abstraction and implementations:traits.rs—PaperSourceasync trait (fetch_paper, fetch_references,fetch_citing_papersdefault no-op, source_name)arxiv_source.rs/arxiv_api.rs/arxiv_utils.rs/html_parser.rs— arXiv metadata + HTML bibliography scraping;arxiv_utils.rsholds the BFS crawler (recursive_paper_search_by_references, takes&mut dyn PaperSource)inspirehep_api.rs—InspireHepClient(350ms rate limit)semantic_scholar_api.rs— S2 source, the only one implementing forward citations (--bidirectional)openalex_source.rs/openalex_bulk.rs— OpenAlex single-paper source and theOpenAlexBulkLoaderbehindbulk-ingestchained_source.rs— comma-separated source chaining (--source arxiv,inspirehep)rate_limiter.rs(governor),text_extractor.rs,search_query_handler.rs
-
database/(ssr) — SurrealDB v3 persistence:client.rs(connect,connect_memory,connect_local),schema.rs(versionedapply_migration_N+schema_migrationstable),queries.rs(PaperRepository),crawl_queue.rs(persistent BFS frontier). -
datamodels/—paper.rs(Paper,Reference,Link,Journal,DataSource; transientciting_papers), plusanalysis.rs,community.rs,community_graph.rs(export-onlyCommunityGraph/ExportedNode/ExportedEdge/LouvainParams),enrichment.rs,extraction.rs,gap_finding.rs,graph_metrics.rs,llm_annotation.rs,progress.rs,similarity.rs. -
data_processing/—graph_creation.rsconvertsVec<Paper>into apetgraph::StableGraph<Paper, f32, Directed>from citation relationships; dedups viastrip_version_suffix(). -
nlp/—preprocessing.rs(tokenization, stop-words) andtfidf.rs(TF-IDF / c-TF-IDF vectors used for community labels and similarity). -
graph_analytics/—community.rs(Louvain viasingle-clustering, ssr-gated),pagerank.rs,betweenness.rs. -
gap_analysis/— literature-based-discovery gap finding:abc_bridge.rs(Swanson ABC),similarity.rs,contradiction.rs,output.rs(ssr). -
analysis/—aggregation.rs(roll-up of per-paper analysis into corpus-level results) andhighlight.rs. -
llm/(ssr) — provider abstraction for semantic extraction:traits.rs,claude.rs,ollama.rs,noop.rs, plusprompt.rs/gap_prompt.rs. -
error.rs—ResynErrorenum:ArxivApi,HtmlDownload,HttpRequest(ssr only),PaperNotFound,InvalidPaperId,NoArxivLink,InspireHepApi,Database,LlmApi,SemanticScholarApi,OpenAlexApi. ImplementsDisplay,Error,From<reqwest::Error>. -
validation.rs— arXiv ID validation (newYYMM.NNNNNand oldcategory/NNNNNNN, optional version suffix) and URL validation. -
utils.rs—strip_version_suffix(),create_http_client()(30s timeout).
main.rs (the single #[tokio::main]) → commands/: crawl.rs, analyze.rs, export.rs, bulk_ingest.rs, serve.rs — one module per CLI subcommand.
Leptos SSR/CSR app: pages/ (dashboard, graph, papers, gaps, methods, open_problems), components/ (search_bar, graph_controls, analysis_controls, crawl_progress, gap_card, heatmap), layout/ (sidebar, drawer), server_fns/ (leptos server functions per domain: papers, graph, gaps, community, similarity, metrics, methods, problems, analysis), and graph/ — the renderer stack: canvas_renderer.rs, webgl_renderer.rs, lod.rs, label_collision.rs, viewport_fit.rs, kmeans.rs, interaction.rs, layout_state.rs, worker_bridge.rs.
forces.rs + barnes_hut.rs force-directed layout, driven from bin/resyn_worker.rs as a gloo web worker.
Workspace-level (Cargo.toml [workspace.dependencies]) unless noted.
- leptos / leptos_router / leptos_meta / leptos-use 0.8 — the web UI (there is no desktop GUI; egui/eframe/fdg were removed)
- leptos_axum + axum + tower-http — SSR host and static serving
- tokio — async runtime (single
#[tokio::main]inresyn-server/src/main.rs) - surrealdb v3 — embedded document-graph DB (
kv-memfor tests,kv-surrealkvfor local persistence) - petgraph 0.7 (default-features off; graphmap/stable_graph/matrix_graph/serde-1) — graph data structures; re-exported from
resyn-coreso downstream crates don't depend on it directly - single-clustering 0.6 — Louvain; ssr-only because it pulls rayon (not WASM-safe)
- arxiv-rs — arXiv API client
- reqwest / scraper / async-trait — HTTP, HTML parsing, async traits (all ssr-gated in core)
- governor — rate limiting
- wasm-bindgen / web-sys / js-sys / gloo-worker / wasm-bindgen-futures — WASM frontend + worker plumbing (WebGL2 + Canvas2D features)
- clap (derive + env) — CLI parsing; dotenvy —
.envloading - stop-words, sha2, chrono, rand, anyhow, regex
- tracing / tracing-subscriber — structured logging
- serde / serde_json — serialization for all data models
- wiremock, tokio-test, http, tower (dev) — HTTP mocking and server test harness
- arXiv source: Each paper requires two HTTP requests — one to arXiv API (metadata) and one to the arXiv HTML page (bibliography references). Both go through
ArxivHTMLDownloaderrate limiting (default 3s). - InspireHEP source: Single API call per paper returns metadata + references with direct arXiv eprint IDs (no HTML scraping needed). Rate limit: 350ms between requests.
- Reference extraction from arXiv HTML parses
<span class="ltx_bibblock">elements. Titles extracted from<em>tags when present, falling back to comma-splitting. - The BFS crawler (
recursive_paper_search_by_references) accepts&mut dyn PaperSource, enabling source-agnostic crawling. - Only arXiv-to-arXiv citation edges are followed. References to Nature/PhysRev/other journals are stored but not crawled.
- SurrealDB persistence: papers stored as
paper:⟨arxiv_id⟩records, citations ascitesrelation edges. Schema is auto-initialized on connection.
Run cargo run --bin resyn -- <subcommand> --help for full argument lists. Key arguments per subcommand:
crawl
| Argument | Default | Description |
|---|---|---|
--paper-id / -p |
2503.18887 |
arXiv seed paper ID |
--max-depth / -d |
3 |
BFS crawl depth |
--rate-limit-secs / -r |
3 |
Rate limit between requests (seconds) |
--source |
arxiv |
Data source: arxiv, inspirehep, semantic_scholar, or comma-separated chain |
--bidirectional |
false |
Fetch forward citations (citing papers) in addition to references; semantic_scholar source only |
--max-forward-citations |
500 |
Cap on citing papers fetched per paper when --bidirectional is set |
--db |
surrealkv://./data |
DB connection string |
analyze
| Argument | Default | Description |
|---|---|---|
--db |
surrealkv://./data |
DB connection string |
--llm-provider |
none | LLM for semantic extraction: claude, ollama, noop |
--force |
false | Re-analyze already-analyzed papers |
export-louvain-graph — exports the Louvain community graph to JSON for external tooling
| Argument | Default | Description |
|---|---|---|
--db |
surrealkv://./data |
DB connection string |
--output |
(required) | Output JSON file path |
--published-before |
none | ISO-8601 date cutoff e.g. 2014-12-31 (inclusive, lexicographic) |
--tfidf-top-n |
50 |
Max TF-IDF terms per node |
Output schema: {louvain_params, corpus_fingerprint, nodes: [{id, community_id, tfidf_vec}], communities: [{community_id, size, tfidf_vec}], edges: [{src, dst, weight}]}. "Other" community papers (community_id = u32::MAX-1) are excluded. Edge weight is 1.0 (uniform). The communities field carries per-community c-TF-IDF vectors for EXP-RS-07 (Sheaves-LBD); old consumers ignore it via serde default. Requires communities to be computed first (analyze runs community detection). See resyn-core/src/datamodels/community_graph.rs for the full type definitions.
Typical Kuramoto-LBD v03 workflow:
# 1. Crawl a corpus (one-time)
cargo run --bin resyn -- crawl --paper-id <seed> --db surrealkv://./data --max-depth 3
# 2. Run analysis (NLP + community detection)
cargo run --bin resyn -- analyze --db surrealkv://./data
# 3. Export for the Python notebook
cargo run --bin resyn -- export-louvain-graph \
--db surrealkv://./data \
--output prototypes/data/research_synergy_pre2015.json \
--published-before 2014-12-31 \
--tfidf-top-n 50
# 4. Run the Python LBD prototypes in ./prototypes/ (venv: prototypes/.venv)Besides the product track, this repo hosts the Dynamical LBD (Gen-4) research thread,
managed by the vault at ~/Repositories/garden. For research phases (exploratory
LBD experiments, EXP-RS-*), follow the vault's research structure on top of the GSD scaffolding:
- Thread state (read by the vault's
/cartographer):.planning/research/THREAD.md(hard core, live hypotheses, kill criteria, claims) +.planning/research/CONVENTIONS.md(append-only convention lock — honor it; append, never edit). Update THREAD.md same-day after every experiment run. - Experiment registry: EXP-RS-* rows in the vault's
wiki/meta/agentic-experiments-research.md. Predictions are pre-registered there before running — never adjusted post-hoc. - Baseline rule: no dynamical method is interesting until it beats the brute-force baseline
(vault:
wiki/concepts/brute-force-lbd-baseline.md) on the shared 10-pair benchmark. - Skills:
/consult research "<question>"— read-only vault lookups;/commission --research— independent falsification of a claim/verdict before accepting it;/chroniclerat session end (self-invoking; auto-files lessons to the vault). - Cartographer channel:
.cartographer-notes.md(gitignored) — proposals from vault research reviews land there; go/kill/pivot decisions are the human's, recorded in the vault hypothesis-ledger. - Prototype workspace (in-repo): ALL research/LBD implementation — Python + Rust prototypes,
scripts,
data/,figures/— lives in-repo at./prototypes/(venv rebuilt fromprototypes/requirements-lock.txt, gitignored; run scripts viaprototypes/.venv/bin/python). The garden holds only management/ideas/decisions (thread state in.planning/, brainstorms, conventions) — no implementation. (Consolidated 2026-07-07, human decision; supersedes the old vault-prototypes split — see CONVENTIONS.md C-21.)
- arXiv rate limiting:
ArxivHTMLDownloaderenforces configurable delays (default 3s) between requests usingtokio::time::sleep. Violating this causes request blocks. - InspireHEP rate limiting:
InspireHepClientenforces 350ms between requests. - Bidirectional crawl mode (
--bidirectional, S2 only): Fetches both backward (references) and forward (citations) for each paper from the Semantic Scholar/citationsendpoint and enqueues newly-discovered citing papers in the BFS queue. Forward-citation edges are persisted viaPaperRepository::upsert_inverse_citations_batchwith the correct direction (citing -> cited). The--max-forward-citations Nflag caps pagination per paper (default 500). Use this mode for pre-2015 cond-mat seeds where S2 backward citations haveexternalIds: null(the graph would otherwise terminate after one hop). - OpenAlex bulk ingest (
bulk-ingestsubcommand): Ingests arXiv-indexed papers in bulk from the OpenAlex REST API (polite pool, ~10 req/s). Auth: OpenAlex's unauthenticated polite pool (mailto UA) still works — no key needed by the API (verified 2026-07-05; the.envkey is a commented-out placeholder). Howeverbulk_ingest::run(resyn-server/src/commands/bulk_ingest.rs) stillprocess::exit(1)s when--api-key/OPENALEX_API_KEYis missing, and sends it as anAuthorization: Bearerheader, so the flag is a requirement of this CLI rather than of OpenAlex. Skips per-paper HTTP calls entirely.upsert_citations_batchdoes not check target paper existence (dangling edges OK).arxiv_id()extracts arXiv IDs from both10.48550/arxiv.*DOIs andlocations[].landing_page_urlmatchingarxiv.org/abs/. Concept IDs:C154945302=ML,C121332964=stat.ML,C41008148=Neural Networks,C26873012=Condensed matter physics,C121864883=Statistical physics. - Paper IDs may have version suffixes (e.g., "2301.12345v2") — these are stripped via
utils::strip_version_suffix()during crawl dedup, graph construction, and DB upserts. - Rust edition 2024, stable toolchain (pinned via
rust-toolchain.toml). - Single async runtime:
main.rshas the only#[tokio::main]. All API/HTML functions areasync fn. - Error handling uses
ResynErrorwith?propagation. The crawler logs warnings for individual failures and continues. - SurrealDB
kv-memfeature compiles as a Rust dependency — no external server needed for tests. - CI runs fmt check, clippy with
-Dwarnings, tests, and coverage via tarpaulin.
- 46 tests total: 32 unit tests + 8 integration tests + 6 database tests
- Unit: paper, graph_creation, arxiv_utils, search_query_handler, validation, utils, inspirehep_api deserialization/conversion
- Integration: wiremock-based arXiv HTML parsing (3), InspireHEP API mocking (5)
- Database: SurrealDB in-memory (upsert, idempotent, exists, version dedup, citations, graph traversal)
- All DB tests use
connect_memory()— no external DB required ArxivHTMLDownloader::with_rate_limit(Duration::from_millis(0))andInspireHepClient::with_rate_limit(Duration::from_millis(0))disable rate limiting in tests
Known pre-existing failures — do NOT chase these as regressions: 7 schema-migration tests fail and cargo clippy --all-targets --all-features reports 4 errors. Both predate any current work (Phase 24-01, migration 12) and are recorded as known-failing in the vault registry. A red suite on a fresh checkout is expected; only failures beyond these are yours.