{
  "markdown": "![Research Scan logo](https://raw.githubusercontent.com/Synectic-Research/research-scan/v0.6.0/assets/research-scan-logo.jpg)\n\n# research-scan\n\nResearch Scan finds and verifies the 5–10 recent papers that matter most for a research question,\nproject, or decision. Starting from a brief or a single question, it retrieves the literature,\nexpands through citation graphs, measures coverage, verifies every DOI against the live record, and\nemits evidence objects — while the agent driving it plans the queries, screens the candidates and\nranks the shortlist. It is an evidence pipeline for a reasoning agent, not an assistant that answers\nquestions.\n\nDocumentation: <https://researchscan.synectic.org>\n\nThe work is split in two, deliberately.\n\n**The CLI is deterministic and model-free.** It retrieves across OpenAlex, Semantic Scholar and\narXiv, walks the citation graph, dedups, counts coverage per criterion, shortlists, verifies every\nDOI against the live record, applies the selection rules and renders the result. It contains no LLM\nSDK and makes no judgement about a paper.\n\n**The agent supplies the cognition.** Planning the queries, scoring each candidate 0–3, writing gap\nqueries when coverage is thin, and reranking the shortlist are all model decisions. The skill\npackages the rubrics they are made against, as plain Markdown in `skills/research-scan/references/`;\nwhatever agent hosts it executes them.\n\nFiles are the interface between the two halves: the filesystem is the protocol boundary between\nreasoning and execution, not a storage detail. That is what lets Claude Code, Codex, Cursor or a\nplain Python loop drive the same chain over the same artifacts.\n\n```\nbrief ─▶ [plan queries] ─▶ retrieve ─▶ [screen 0–3] ─▶ expand ─▶ [screen] ─▶ coverage\n      ─▶ [gap round, if coverage is thin] ─▶ shortlist ─▶ [rerank] ─▶ verify ─▶ emit\n```\n\nBracketed steps are the agent's; the rest are CLI stages.\n\nTwo things bound the result. A **purpose** — `build`, `research` or `orient` — decides what a paper\nhas to do to earn a slot. A **profile** — `quick`, `standard` or `deep` — decides how much the scan\ncosts. Both are described under [How it works](#how-it-works).\n\nOutput is a run directory of validated JSON plus a Markdown brief you can hand to a human, in which\nevery paper's DOI, title, year and first author were checked against the live record.\n\n## Install\n\nPython ≥ 3.11 and [uv](https://docs.astral.sh/uv/).\n\n```bash\nuvx research-scan doctor          # run any command without installing\nuv tool install research-scan     # or put it on PATH permanently\n```\n\n## Configure\n\n`configure` asks for each credential, shows what is already set, writes\n`~/.config/research-scan/.env` with mode `0600` inside a `0700` directory, and finishes by running\n`doctor`.\n\n```bash\nresearch-scan configure          # `setup` is an alias for the same command\n```\n\nKeys are read without echo. Re-running is safe: every variable shows its current value masked to the\nlast four characters, Enter keeps it, typing replaces it. Anything already in the file that\n`configure` does not ask about — comments included — is left exactly as it was. If stdin is not a\nterminal it prints the file to write and exits 2 rather than hanging, which is the behaviour you\nwant in a Dockerfile or a provisioning script.\n\n| Variable | Needed for |\n|---|---|\n| `OPENALEX_API_KEY` | OpenAlex, the primary source. **Required**; `doctor` exits 3 without it. |\n| `OPENALEX_MAILTO` | Strongly recommended. Raises OpenAlex to 5 req/s and joins the Crossref polite pool. |\n| `S2_API_KEY` | Recommended. Semantic Scholar search and citation-graph expansion work without it, throttled to roughly a third of the rate; `doctor` warns. |\n| `NCBI_API_KEY` | Optional. PubMed only: 10 req/s instead of 3. |\n\nTo write the file by hand instead, `.env.example` in the repo is the template. Process environment\nbeats `~/.config/research-scan/.env`, which beats a repo-local `./.env`.\n\n## Check it works\n\n`doctor` invokes every source live with the cache bypassed, rather than listing what it thinks is\nconfigured.\n\n```bash\nresearch-scan doctor             # a four-line summary and a verdict\nresearch-scan doctor --verbose   # every check, with timings and paths\nresearch-scan doctor --json      # machine-readable; this is the CI/agent interface\n```\n\n```\nResearch Scan <version>\n✓ configuration\n✓ OpenAlex   ✓ Semantic Scholar   ✓ Crossref   ✓ arXiv   ○ PubMed\n  PubMed: endpoint reachable; source routed for biomed but not built\n✓ writable run store\nReady.\n```\n\nExit 0 means go. Exit 3 means a mandatory check failed; see [Troubleshooting](#troubleshooting). The\nchecks and the exit code are identical in all three output modes — only the presentation differs.\n\n`○` marks a source that is routed but has no retrieval adapter — see the\n[sources table](#supported-sources). The probe still runs and still reports honestly: PubMed's\nrow says the E-utilities endpoint answered, which is a different claim from being able to\nretrieve from it. `doctor --json` carries the same fact as `sources_not_built`, alongside a\n`providers` map whose keys and values are unchanged.\n\n## Your first scan\n\nWith the skill installed, ask for a scan in your agent:\n\n```\n/research-scan \"I am designing a financial education product for young adults. \nResearch question: What interventions have the strongest recent evidence for increasing \nlong-term savings behavior?\n\nContext:\n- I am interested in mechanisms that change real-world behavior, not just \nfinancial literacy scores.\n- Prioritize randomized trials, large field studies, and strong causal evidence.\n- Include evidence that challenges the assumption that education alone works.\n\nFind the 10 papers that should shape our product decisions. Explain why each paper matters, what it \nfound, and what evidence gaps remain.\" --profile standard\n```\n\nA one-line question works. A brief with context works better, because the planning step maps each section of it onto a specific part of the search plan. The template is\n`skills/research-scan/examples/brief.example.md` — a worked `build` brief — and\n`brief.research-example.md` is the same template at `Purpose: research`.\n\n```\n/research-scan research/my-brief.md --profile standard --top 10\n```\n\n| Brief section | What it feeds |\n|---|---|\n| **`Purpose:` line** | Which sub-criteria the plan derives and what `why_it_matters` must argue. `build`, `research` or `orient`; omit it and the agent infers one. |\n| **What this is about** | The `brief_summary` and the domain routing. Say the setting, not just the topic. |\n| **What we need to decide or answer** | The sub-criteria every paper is scored against. Name the actual open choices, or the actual questions. |\n| **What we already believe (the premise)** | The `contradictory` query. A scan that only confirms the premise is not evidence. |\n| **Exclusions** | `queries.json.must_not`, enforced in code at word boundaries — never as NOT-terms in query text. |\n| **Known papers or authors** | `anchors`: pinned into the pool, exempt from the filters and the cap, always used as expansion seeds. |\n\nResults land in `research/scans/<YYYY-MM-DD>-<slug>/`. See [Outputs](#outputs).\n\nFull argument list:\n\n```\n/research-scan <brief.md | \"question\"> [--profile quick|standard|deep] [--top N] [--foundational N]\n                                       [--from YYYY-MM] [--to YYYY-MM] [--slug name]\n                                       [--domain behavioral|cs|biomed|general]\n                                       [--max-candidates N] [--per-query N]\n```\n\n## Connect your agent\n\nTwo front doors to the same pipeline. The skill is where the rubrics live, so it is the one that\ncarries the screening and reranking cognition; the MCP server exposes the same four decision points\nas tools for any client that speaks MCP.\n\n### Claude Code — the skill\n\nAny one of these; all three point at the same `skills/research-scan/`.\n\n```bash\n# 1. symlink a clone — edits to the rubrics take effect immediately\nln -s \"$PWD/skills/research-scan\" ~/.claude/skills/research-scan\n\n# 2. load the repo as a plugin, per invocation (it carries .claude-plugin/plugin.json)\nclaude --plugin-dir /path/to/research-scan\n\n# 3. skills CLI\nnpx skills add Synectic-Research/research-scan\n```\n\n### Claude Code — the plugin\n\nThe plugin installs the skill and configures the `uvx` MCP server in one step; the `.mcp.json` route\nbelow stays the path for non-plugin clients such as Claude Desktop and Cursor.\n\n```bash\n/plugin marketplace add Synectic-Research/research-scan\n/plugin install research-scan@synectic\n```\n\n### Any MCP client — stdio\n\n`research-scan mcp` speaks MCP on stdin/stdout and nothing else. It reads no token and needs none:\nthe process is trusted because your client launched it. Nothing extra to install — the server\nships with the package.\n\n```json\n{\n  \"mcpServers\": {\n    \"research-scan\": {\n      \"command\": \"uvx\",\n      \"args\": [\"research-scan\", \"mcp\"]\n    }\n  }\n}\n```\n\n| Client | Where that goes |\n|---|---|\n| Claude Code | `.mcp.json` in the project root |\n| Claude Desktop | `claude_desktop_config.json` |\n| Cursor | `.cursor/mcp.json` |\n\nFour tools, one per model decision the pipeline needs: `scan_start` (you supply the query plan),\n`scan_continue` (screening scores, gap queries, or a page of rerank entries), `scan_verify` and\n`scan_result`. They are not a chat endpoint over the pipeline: each is a decision point where a\njudgement is needed, and the client's model supplies exactly the cognition the skill would — same\nstage order, same artifacts. The adapter drives the CLI as a subprocess and reads its exit codes.\nRun `research-scan mcp --help` for the other transport.\n\n## How it works\n\nEach stage is idempotent and re-runnable: change `queries.json` or pass a flag and re-run from the\nstage it affects. The CLI owns `candidates.json`, `shortlist.json`, `manifest.json` and\n`evidence.*`; the agent owns `queries.json`, `screen.json` and `ranked.json`. Neither edits the\nother's files.\n\n### Purpose\n\nDeclared on the brief's `Purpose:` line, or inferred. It decides which sub-criteria the plan derives,\nwhat screening counts as relevant, and what `why_it_matters` has to argue.\n\n| Purpose | A paper earns its place by | Example |\n|---|---|---|\n| `build` | moving a design or plan decision | \"What should we know before we design the enrolment flow?\" |\n| `research` | changing what we believe, what we would test, or how we would measure it | \"What is the strongest recent evidence that default enrolment affects how much people save?\" |\n| `orient` | being something a newcomer must know, recent first | \"I am new to mechanistic interpretability — what must I read, recent first?\" |\n\n### Profiles\n\nOne flag sets how much the scan costs. `--profile` belongs to `init` and is recorded in the manifest;\nan explicit flag still overrides it — `--per-query 60` means 60 whatever the profile says.\n\n| Profile | Per query | Pool cap | Out-of-window total | Gap round |\n|---|---|---|---|---|\n| `quick` | 20 | 250 | 12 | never |\n| `standard` (default) | 40 | 450 | 20 | when coverage is uneven |\n| `deep` | 40 | 450 × sources ÷ 2 | 30 | always |\n\nThe out-of-window total is a budget for the whole run, not a per-stage allowance. Start at\n`standard`; use `quick` for \"what is out there\" and `deep` when a miss is expensive. Measured recall\nfor each is under [Measured results](#measured-results).\n\n### The gap round\n\nRuns at most once, and on `standard` only when `coverage` reports that the criteria are unevenly\ncovered or that a query came back nearly empty — otherwise it is skipped and the report says so.\n`coverage` counts, per sub-criterion, how many papers screening kept; the agent writes one or two\nqueries against the criteria that came back thinnest; `retrieve --round 2` and `expand --round 2`\nadd what they find. Round 1's papers and their scores are never discarded — the pool only grows.\n\n## Supported sources\n\n| Source | Used for | Status |\n|---|---|---|\n| **OpenAlex** | Primary search, metadata, retraction flag, graph fallback | Implemented. Requires a key. |\n| **Semantic Scholar** | Search, and citation-graph expansion over references, citations and recommendations | Implemented. Key optional, throttled without one. |\n| **arXiv** | Extra source for `cs`, and for `general` when a query is a method query | Implemented. |\n| **PubMed** | Routed for `biomed` | **Routed but not built yet.** A biomed scan runs on OpenAlex + Semantic Scholar and records `unavailable: true` in `manifest.json` — routed-but-missing is recorded, never dropped quietly. |\n| **Crossref** | DOI verification only; never a retrieval source | Implemented. Verification degrades to OpenAlex if it is unreachable. |\n\n`--domain` picks the routing: `behavioral` and `general` → OpenAlex + Semantic Scholar; `cs` → those\nplus arXiv; `biomed` → those plus PubMed. `--sources` overrides the map outright.\n\n## Measured results\n\nTwo curated golden topics — behavioural economics and LLM literature-search agents — scored at the\ncandidates stage and by an independent judge model. These are two topics, not a benchmark against\nother tools.\n\n| Profile | Candidates screened | Recall (topic 1 · topic 2) | ≈ wall clock |\n|---|---|---|---|\n| `quick` | ~355 | 7/10 · 4/6 | 20–25 min |\n| `standard` | ~570 | 8/10 · 5/6 | 35–40 min |\n| `deep` | 575–805 | 9/10 · 5/6 | 40–55 min |\n\nJudged precision of the in-window top 10: **0.875 on both topics**, by an independent judge.\n\nRecall climbs with the pool and efficiency falls with it: `quick` returns 0.199 recall per 100\ncandidates screened on topic 1 where `deep` returns 0.157. What separates the profiles most is the\nout-of-window budget (12 / 20 / 30) — topic 1's remaining misses are all pre-window classics, and\neach step up admits one more.\n\nFull tables, what was tried and reverted, and the rules the numbers obey:\n[`docs/measurements.md`](https://github.com/Synectic-Research/research-scan/blob/main/docs/measurements.md).\n\n## Outputs\n\nEverything lands in `research/scans/<YYYY-MM-DD>-<slug>/`:\n\n| File | What it is |\n|---|---|\n| `evidence.json` | The deliverable: one `EvidencePacket` per paper — metadata, rerank scores, key finding, why it matters, limitations, and the verification record. |\n| `evidence.md` | The same list rendered for a human. Foundational classics render after the current work, ranks running straight through, so a 2004 paper reads as context rather than as the answer. |\n| `evidence.bib` | BibTeX for the emitted papers. Suppress with `--no-bib`. |\n| `manifest.json` | The audit trail. Every drop — retracted, `must_not`, type filter, pool cap — and every source that failed after retries is counted here. Nothing is capped silently. |\n| `coverage.json` | Per sub-criterion, how many kept papers cover it, split by query type and source, plus per-query yield and per-seed expansion precision. One snapshot per round, so what the gap round recovered is a delta. Rendered as a Coverage section in `evidence.md`. |\n| `queries.json`, `candidates.json`, `screen.json`, `expanded.json`, `shortlist.json`, `ranked.json` | The intermediate stages, all schema-validated. |\n| `retrieval.log.jsonl`, `expansion.log.jsonl`, `verify.log.jsonl` | Per-stage structured logs. |\n\nA paper whose DOI did not resolve, or whose title, year or first author disagreed with the live\nrecord, is **flagged, not silently repaired**: it ships with `mismatches[]` and an `[UNVERIFIED —\ncheck manually]` marker. Only retraction removes a paper.\n\nPrint any data contract with `research-scan schema --name <Model>`, or all of them as Markdown with\n`research-scan schema --md`.\n\n## Cost and time\n\nWall clock tracks the candidate pool, because screening is the long pole. Measured end to end\n(`counts.wall_clock_s`, `init` → `emit`):\n\n| Pool | Screening batches | Wall clock |\n|---|---|---|\n| ~675 candidates — cs, 3 sources at the default cap | ~27 | **37 min** |\n| ~250 candidates — 2 sources at the default cap | ~10 | **22 min** |\n| ~120 candidates — `--max-candidates 120` | 5 | **19 min** |\n\nOpenAlex bills ≈ $0.008 either way — `per_page` is free depth, so pool size costs almost nothing in\nAPI money.\n\n**Agent tokens are the real cost**, and they do not fall as fast as the pool does: the 120-candidate\nrun still cost ≈ $5.70 in frontier-model tokens and ran only 3 minutes quicker than the\n250-candidate one, because reranking (52 full records, regardless of pool size) and fixed stage\noverhead dominate once screening is small. `--max-candidates` is the right dial for a runaway cs\npool, not a way to make a scan cheap.\n\nThe default cap scales with the number of built sources (450 × n/2, so 675 for cs, 450 for two).\n`--max-candidates` and `--per-query` belong to `retrieve` and override it per run. Both are run\nflags, not config.\n\n## Headless\n\nThe skill returns a `ScanSummary`, so a scan can run unattended and hand structured JSON to whatever\ncomes next:\n\n```bash\nclaude -p \"Use the research-scan skill to scan brief.md with --slug nightly --top 5. \\\n           When it finishes, return its ScanSummary as your final answer.\" \\\n  --plugin-dir /path/to/research-scan \\\n  --allowedTools \"Bash(research-scan *),Read,Write,Edit,Glob,Grep\" \\\n  --output-format json \\\n  --json-schema \"$(research-scan schema --name ScanSummary)\" \\\n  > scan.json\n\njq '.structured_output.counts' scan.json\n```\n\n**Ask for the skill in prose; do not use a bare `/research-scan …` slash command here.** The skill\ndeclares `context: fork`, so a bare slash command runs entirely inside the fork and the session\nreturns `num_turns: 0` with `result: \"Command completed\"` — the scan succeeds and writes its run\ndirectory, but there is no top-level assistant turn for `--json-schema` to bind to and\n`structured_output` comes back `null`. Phrasing it as an instruction makes the session invoke the\nskill, receive the fork's report and emit the `ScanSummary` itself, which is what the schema\nattaches to. Verified both ways on 2026-08-19.\n\nTwo more flags to get right:\n\n- `--plugin-dir` is unnecessary if the skill is already installed under `~/.claude/skills/`. Never\n  combine it with `--bare`: bare mode skips `--plugin-dir`, so the skill would not load at all.\n- `--allowedTools` must carry `Bash(research-scan *)`, `Read`, `Write`, `Edit`, `Glob` and `Grep`.\n  `Glob` is not optional — the skill globs `screen-batches/` to find the batches to score.\n\n## Not for\n\n**Systematic reviews.** There is no PRISMA trail and no workflow for screening thousands of records.\n\n**Manuscript citation management.** `evidence.bib` is a courtesy, not a library. Use a citation\nmanager.\n\n**Full-text work.** Retrieval and screening run on titles, abstracts and metadata. No PDF is\nfetched, parsed or read.\n\n**Google Scholar, Scopus, Web of Science, PsycInfo** are not sources and are not planned: none\noffers a personal-key API or a ToS-compatible path.\n\n## Troubleshooting\n\n**`doctor` exits 3.** A mandatory check failed — read the check name and fix that, rather than\nworking around it. A missing `OPENALEX_API_KEY` is a `FAIL`, and the OpenAlex probes are then\nreported as `SKIP` rather than as passing, because they were never run. A missing `S2_API_KEY` or\n`NCBI_API_KEY` is only a `WARN`: the scan still runs, throttled. The default output names what to\ndo; `--verbose` gives the full check list with timings, and `--sources openalex,s2` narrows what is\nchecked.\n\n**Reading `doctor` from a script or an agent.** `doctor --json` is the interface, and its keys are\nstable. `ready` is the boolean, `providers` maps each checked source to `ok` / `warn` / `fail` /\n`skip` — `skip` meaning the probe never ran, which is never the same as passing — and `checks`\ncarries every individual result. The exit code is the same in all three output modes.\n\n**`arxiv HTTP 429, cs/physics routing skipped`.** arXiv rate-limits aggressively (the client already\nholds to 1 request / 3 s) and returns 429 under load. This is a `WARN`, not a failure: cs scans fall\nback to OpenAlex + Semantic Scholar, which carry arXiv preprints anyway — you lose the same-day\nlistings and one origin signal, not coverage. Re-run later if recency matters; per-source query\ncounts and failures are in `manifest.json` under `retrieval.per_source`.\n\n**Semantic Scholar backlog.** S2 answers 429/503 when its queue is deep. `http.py` retries and then\nreturns the last response rather than raising — a bad status is data. Graph expansion falls back to\nOpenAlex's bare-id reference list, which works but cannot be ranked (ordering it would cost a\nmetadata fetch per reference), so expansion recall drops. `expansion.log.jsonl` records it per call.\n\n**A stage exits 2.** An agent-written file failed validation. The error lists every offending path;\nunknown keys are rejected everywhere, so a typo surfaces instead of being swallowed. Fix the file and\nre-run that stage.\n\n**`research-scan mcp` cannot import the server.** `fastmcp` is a core dependency, so this means an\nincomplete install rather than a missing option — reinstall with `uv tool install research-scan`.\n`research-scan version --json` reports `\"mcp\": \"enabled\"` on an intact install.\n\n## Shell completion\n\n```bash\neval \"$(research-scan completion bash)\"   # add to ~/.bashrc\neval \"$(research-scan completion zsh)\"    # add to ~/.zshrc\nresearch-scan completion fish > ~/.config/fish/completions/research-scan.fish\n```\n\n## Development\n\n```bash\ngit clone https://github.com/Synectic-Research/research-scan\ncd research-scan\nuv sync\nuv run ruff check && uv run pytest -q     # the before-commit gate\nuv run pytest -m live                     # the only tests that touch the network\nuv tool install --editable .              # put research-scan on PATH, tracking the working tree\n```\n\n`src/research_scan/schema.py` is the source of truth: it generates the JSON Schema, the contract\ndocs the agent reads (`skills/research-scan/references/schemas.md` — regenerate it in the same\ncommit, a test compares them), and the exit-2 error messages.\n\n`AGENTS.md` holds the operating doctrine, [`docs/measurements.md`](https://github.com/Synectic-Research/research-scan/blob/main/docs/measurements.md) the\nmeasured results behind the current defaults, and `eval/` the golden-set harness\n(`research-scan eval --topic <t> --run <dir>`).\n\n[`drivers/`](https://github.com/Synectic-Research/research-scan/blob/main/drivers/README.md) holds\nexperimental reference implementations of a cognition engine — code that produces the judgements a\nscan needs, for people who want one driven by something other than a hosting agent. They live in\nthe repository only: nothing there is installed by the package, imported by it, or shipped in its\nsdist or wheel, and no engine protocol ships in this release.\n\n[Documentation](https://researchscan.synectic.org) · [`CONTRIBUTING.md`](https://github.com/Synectic-Research/research-scan/blob/main/CONTRIBUTING.md) · [`SECURITY.md`](https://github.com/Synectic-Research/research-scan/blob/main/SECURITY.md) ·\n[`CHANGELOG.md`](https://github.com/Synectic-Research/research-scan/blob/main/CHANGELOG.md) · [`CODE_OF_CONDUCT.md`](https://github.com/Synectic-Research/research-scan/blob/main/CODE_OF_CONDUCT.md) ·\n[`CITATION.cff`](https://github.com/Synectic-Research/research-scan/blob/main/CITATION.cff) · [`LICENSE`](https://github.com/Synectic-Research/research-scan/blob/main/LICENSE) (Apache-2.0)\n\nDependencies keep their own licenses; [`THIRD_PARTY_LICENSES.md`](https://github.com/Synectic-Research/research-scan/blob/main/THIRD_PARTY_LICENSES.md) says what\nthey are and how to regenerate the table.\n\n<!-- mcp-name: org.synectic/research-scan -->\n",
  "bytes": 23553,
  "sha": "ae54b6ac378065cc00fd0d776dca0a215d06e3ffea9e3f6b62656d52f9fda2c0",
  "repo_slug": "synectic-research/research-scan",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_org_synectic_research_scan_58f83c6b/readme"
}