You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The dashboard settings page could not load the provider model list over
network access: GET /api/models gated on the loopback-only Host guard even
when a dashboard token was configured, so every authenticated request from a
non-loopback browser fell through to a placeholder route instead of proxying
to the provider. The guard now yields once a token is configured, mirroring
the rest of the router.
Changed
crates.io publish is a cargo-dist publish job on the same tag-triggered
Release run as Homebrew (publish-jobs = ["homebrew", "./publish-crates"]),
matching alcove. The separate release-extras workflow is gone.
Install research-agent 0.2.5
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/epicsagas/research-agent/releases/download/v0.2.5/research-agent-installer.sh | sh
research query now prints a Source: line per paper (URL, else DOI,
else arXiv abs URL, else local PDF path) so the original document is
one glance away from the hit list.
Install research-agent 0.2.4
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/epicsagas/research-agent/releases/download/v0.2.4/research-agent-installer.sh | sh
Claude Code printed research-agent: hooks.json: unknown key "$schema" ignored at every TUI start: the plugin hooks loader only accepts the hooks key, and the editor-completion $schema line added on 2026-09-08
tripped it. The line is gone.
Install research-agent 0.2.3
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/epicsagas/research-agent/releases/download/v0.2.3/research-agent-installer.sh | sh
arXiv ingest no longer dies on hostile PDFs. The release profile set panic = "abort", which silently disabled the catch_unwind guards around
PDF text extraction — one paper with a malformed embedded CMap
(bad length of hexstring inside adobe-cmap-parser) killed the whole
ingest after its metadata row was committed, permanently missing topic
linking. The abort setting is gone; a PDF that panics during extraction is
now reported as a per-paper warning and the run continues.
Re-ingesting no longer orphans topic links. The ingest pipeline used to
return only newly inserted papers, so a re-run linked nothing ("Ingested 0 /
Linked 0") — papers whose linking was missed by a crashed run stayed
unlinked forever without manual SQL. The pipeline now returns every fetched
paper that is in the library (carrying its stored id); topic linking and
body download are idempotent, so a re-run of the same query repairs the
gap. Applies to the CLI (arxiv/s2/openalex/europepmc/preprints/zotero/pdf)
and the MCP ingest tool, which now reports ingested (new) and in_library (total fetched) separately.
Changed
Gap analysis and keyword enrichment retry once on a format-violating LLM
response, echoing the offending reply back with the line format restated
(free/OpenRouter models leak reasoning and ignore formats). A second
consecutive violation fails with an explicit "nothing was saved (no stub
rows were written)" error instead of a silent empty result.
Install research-agent 0.2.2
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/epicsagas/research-agent/releases/download/v0.2.2/research-agent-installer.sh | sh
crates.io channel: research-agent is registered on crates.io (initial
0.1.0 by hand) and future tags publish automatically via the release-extras workflow. Homebrew tap publishing is single-owner
(cargo-dist's own job). Cargo.lock is now committed for reproducible
release builds.
Install research-agent 0.2.1
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/epicsagas/research-agent/releases/download/v0.2.1/research-agent-installer.sh | sh
The one-line installers (install.sh / install.ps1) looked for research-<target> archives but cargo-dist uploads research-agent-<target>
ones, so every download 404'd. Found by running the released installer
end-to-end.
Changed
Release pipeline: the tag-scoped release-extras workflow publishes to
crates.io (CARGO_REGISTRY_TOKEN) and the Homebrew tap (HOMEBREW_TAP_TOKEN),
each a green no-op until its secret is configured; a missing tap token no
longer marks the whole release run failed; CI cancels superseded runs.
Added
Dashboard UI overhaul: light theme alongside the dark reading room, a
mobile layout, full-height modals, and rich text rendering. The UI is
localized to all ten README languages, with a language picker in the sidebar.
Fixed
The dashboard language dropdown no longer overflows the viewport in narrow
sidebars.
Session-start auto-install hook: the installer download now times out
(10 s connect / 60 s transfer) instead of hanging the session on a stalled
network, runs from a private temp directory, and tolerates redirect
responses without a Location header. The Claude hook command guards on node being present. Grok installs find the plugin version even without a .claude-plugin/ directory.
install.sh / install.ps1: network timeouts on every download, PowerShell
temp dir is cleaned up on failure too, TLS 1.2 pinned for old Windows
PowerShell 5.1 builds.
Dashboard hardening: CDN scripts (marked, DOMPurify, KaTeX) are pinned and
SRI-verified, all responses carry a Content-Security-Policy, and the
dashboard token is compared in constant time.
PDF filenames are built from a sanitized arXiv id so a hostile id from
third-party metadata cannot write outside the PDF directory.
arXiv search queries are percent-encoded (matching the other sources), so &, #, or % in a query can no longer alter the request.
Removed
Embedding search subsystem (local ONNX via fastembed, and remote OpenAI embeddings): hybrid_search, the Turbovec vector index, the [search] config section, and the embedding-model step of research init are all gone. The vector channel built a full ONNX session per command — the model reloaded on every research query, and MCP stdio multiplied that by process count, which is how a 53-paper corpus reached 22 GB RSS. Measured on a 117-paper library, research query went from minutes to 0.75 s and peak RSS from gigabytes to 13 MB. FTS5 with generated keywords (below) covers the paraphrase recall the vector channel existed for. Drops fastembed, ort, turbovec, tokenizers, hf-hub, onig, safetensors and ndarray from the dependency tree. Existing configs keep their [search] section — it is ignored, not an error. Leftover ~/.research/embeddings.idx, embeddings.state.json, embeddings.meta.json and ~/.research/models/ are not deleted automatically; removing them by hand reclaims a few hundred MB.
Added
research enrich — search keywords for paraphrased queries: FTS5 matches the words a paper contains; keywords cover the ones it does not (synonyms, expanded acronyms, alternative phrasings), stored in a new keywords column that the FTS index reads. Three paths, all first-class: with [llm] configured the CLI batches its own calls; without one it prints the papers needing keywords so a host agent (Claude Code, any MCP client) can generate them and write them back through research enrich <ID> --keywords "..." or the enrich_paper MCP tool; a plain CLI user who never runs it keeps working lexical search. Keywords are deliberately separate from tags, which are the user's own and get pushed to Zotero. New MCP tools papers_missing_keywords and enrich_paper; query_papers keeps its input schema. Schema version 3 adds the column and recreates papers_fts (a virtual table made with IF NOT EXISTS keeps its old shape otherwise). A golden-query test asserts that six paraphrase queries miss before enrichment and land in the top 3 after.
Web dashboard: research dashboard starts a GET-only HTTP server bound to 127.0.0.1 (default port 7777, [dashboard] port in config to change) that serves a single embedded HTML page plus read-only JSON endpoints over the workspace database — library counts, the topic tree, and the paper table with reading status and ratings. A PATCH endpoint updates reading_status from the UI. Binding to a non-loopback host requires an access token (generated into config.toml), defended against DNS rebinding by a loopback-only Host check. GET-only for reads and request routing is a pure route function, so it is tested without sockets.
Zotero tag push (write slice): research export --to zotero merges each library paper's tags into its Zotero item over the local API, matched by normalized DOI. Dry-run is the default and prints per-paper actions with zero writes; --apply writes, tagging on Zotero's own confirmation dialog for authorization ("Always Allow" for batches — confirmations are capped at five per minute). Items changed in Zotero since the read are skipped and counted, never merged: there is no UI to resolve a conflict. Deliberately CLI-only — no MCP tool, since an agent writing to a user's library is a trust boundary. Reading status and rating stay local: Zotero has no fields for them.
Performance benchmarks: cargo bench (criterion) measures lexical query, FTS index rebuild, and body-evidence snippets over a deterministic 500-paper corpus — no network, fully offline. benches/run.sh reports the medians as an informational performance dimension for the eval gate; timing never turns the gate red, because absolute thresholds are machine-dependent and shared CI runners swing 10-30%. Same-machine regression gating is deliberately deferred until a reference machine exists (ROADMAP).
First-run onboarding: research init in a terminal now walks through the settings that matter — database location, the LLM provider for gap analysis and reports (curated: Anthropic, OpenAI, DeepSeek, OpenRouter, Ollama, LM Studio, plus a custom OpenAI-compatible entry), and the env var holding its key. API keys are never typed into the wizard: it asks for the env var name and reports whether it is set. Ollama and LM Studio are probed (2s timeout) so you pick from models that actually exist. Re-running init offers existing values as defaults instead of resetting. research init --no-onboard (or any non-terminal stdin) skips the prompts, so scripts are never blocked. README documents the flow.
Self-documenting config.toml: Config::save now writes a template in which every key of the optional [llm] section appears — set values as live TOML, unset ones commented out with their defaults — so the file alone shows everything that is configurable. Template output is guaranteed to parse back (toml::from_str round-trip tests).
Fixed
research init no longer wipes an existing config.toml (it used to rewrite defaults over any configured [llm]). Non-interactive runs leave an existing file byte-for-byte untouched.
Zotero read source: research ingest --source zotero reads papers from a running Zotero instance over its local API (http://localhost:23119/api/) — the item-to-Paper mapping is shared with the export-file importer, so abstracts, tags, creators, venue, and normalized DOIs all survive the trip. Query-less reads pull the whole library; queries search all fields (qmode=everything); pages over Zotero's 100-item page cap. Needs Zotero running with "Allow other applications on this computer to communicate with Zotero" enabled (a 403 error names this); ZOTERO_BASE_URL overrides the endpoint for non-default installs. Not part of --source all: a personal library is not a discovery source.
Page anchors and evidence snippets for PDF bodies: PDF ingest now extracts per page and writes <!-- page N --> markers into the stored body, so a match can be traced back to where it appeared. A page that fails mid-document triggers a warning instead of silently truncating the body (the extraction API treats a failed page as end-of-document). research query <q> --evidence prints the matching body text with its section and page, and the MCP paper_body tool accepts a query parameter that returns located snippets instead of the whole body (far cheaper on an agent's context). Bodies stored before this change keep working and still resolve their section; research reingest --missing-pages re-extracts them in place, skipping papers whose source PDF has moved.
Importer field recovery: research import now reads abstracts and keywords/tags from BibTeX and CSL-JSON, and accepts Zotero's native JSON export (dispatched by content, since it and CSL-JSON are both .json). Abstracts matter most here: the field feeds gap analysis and the search index, so imported papers previously landed semantically empty next to ingested ones.
Changed
Papers without a DOI no longer duplicate on re-ingest. The duplicate check is now one shared identity function used by every ingest path (research ingest, research import, and --source pdf), keyed by normalized DOI first, then by the source PDF path, then by normalized title for papers that carry neither. Re-running a whole-library Zotero read or a repeated PDF directory used to insert a fresh row per DOI-less item on every run; the PDF path had no duplicate check at all. Documented trade-off: two genuinely different papers sharing one normalized title, with no DOI and no path, collapse to one row.
DOIs are normalized (resolver prefix stripped, lowercased) before import dedupe, so 10.1/X, https://doi.org/10.1/X, and 10.1/x resolve to one paper instead of three rows.
Fixed
CSL-JSON import now accepts Zotero's real exports: Zotero writes issued.date-parts as strings ([["2023"]], which the CSL-JSON schema allows) an...